Make all timing stuff tz-aware.
--- /dev/null
+from django.contrib import admin
+from .models import Offer, Perk, Funding
+
+admin.site.register(Offer)
+admin.site.register(Perk)
+admin.site.register(Funding)
--- /dev/null
+# -*- coding: utf-8 -*-
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+ # Adding model 'Offer'
+ db.create_table('funding_offer', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('author', self.gf('django.db.models.fields.CharField')(max_length=255)),
+ ('title', self.gf('django.db.models.fields.CharField')(max_length=255)),
+ ('slug', self.gf('django.db.models.fields.SlugField')(max_length=50)),
+ ('book_url', self.gf('django.db.models.fields.URLField')(max_length=200, blank=True)),
+ ('redakcja_url', self.gf('django.db.models.fields.URLField')(max_length=200, blank=True)),
+ ('target', self.gf('django.db.models.fields.DecimalField')(max_digits=10, decimal_places=2)),
+ ('start', self.gf('django.db.models.fields.DateField')()),
+ ('end', self.gf('django.db.models.fields.DateField')()),
+ ))
+ db.send_create_signal('funding', ['Offer'])
+
+ # Adding model 'Perk'
+ db.create_table('funding_perk', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('offer', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['funding.Offer'], null=True)),
+ ('price', self.gf('django.db.models.fields.DecimalField')(max_digits=10, decimal_places=2)),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=255)),
+ ('description', self.gf('django.db.models.fields.TextField')(blank=True)),
+ ))
+ db.send_create_signal('funding', ['Perk'])
+
+ # Adding model 'Funding'
+ db.create_table('funding_funding', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('offer', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['funding.Offer'])),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=127)),
+ ('email', self.gf('django.db.models.fields.EmailField')(max_length=75)),
+ ('amount', self.gf('django.db.models.fields.DecimalField')(max_digits=10, decimal_places=2)),
+ ('payed_at', self.gf('django.db.models.fields.DateTimeField')()),
+ ))
+ db.send_create_signal('funding', ['Funding'])
+
+ # Adding M2M table for field perks on 'Funding'
+ db.create_table('funding_funding_perks', (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('funding', models.ForeignKey(orm['funding.funding'], null=False)),
+ ('perk', models.ForeignKey(orm['funding.perk'], null=False))
+ ))
+ db.create_unique('funding_funding_perks', ['funding_id', 'perk_id'])
+
+
+ def backwards(self, orm):
+ # Deleting model 'Offer'
+ db.delete_table('funding_offer')
+
+ # Deleting model 'Perk'
+ db.delete_table('funding_perk')
+
+ # Deleting model 'Funding'
+ db.delete_table('funding_funding')
+
+ # Removing M2M table for field perks on 'Funding'
+ db.delete_table('funding_funding_perks')
+
+
+ models = {
+ 'funding.funding': {
+ 'Meta': {'ordering': "['-payed_at']", 'object_name': 'Funding'},
+ 'amount': ('django.db.models.fields.DecimalField', [], {'max_digits': '10', 'decimal_places': '2'}),
+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '127'}),
+ 'offer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['funding.Offer']"}),
+ 'payed_at': ('django.db.models.fields.DateTimeField', [], {}),
+ 'perks': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['funding.Perk']", 'symmetrical': 'False'})
+ },
+ 'funding.offer': {
+ 'Meta': {'ordering': "['-end']", 'object_name': 'Offer'},
+ 'author': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'book_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
+ 'end': ('django.db.models.fields.DateField', [], {}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'redakcja_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'}),
+ 'start': ('django.db.models.fields.DateField', [], {}),
+ 'target': ('django.db.models.fields.DecimalField', [], {'max_digits': '10', 'decimal_places': '2'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+ },
+ 'funding.perk': {
+ 'Meta': {'ordering': "['-price']", 'object_name': 'Perk'},
+ 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'offer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['funding.Offer']", 'null': 'True'}),
+ 'price': ('django.db.models.fields.DecimalField', [], {'max_digits': '10', 'decimal_places': '2'})
+ }
+ }
+
+ complete_apps = ['funding']
\ No newline at end of file
--- /dev/null
+# -*- coding: utf-8 -*-
+# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+from django.core.urlresolvers import reverse
+from django.db import models
+from django.utils.translation import ugettext_lazy as _, ugettext as __
+from datetime import date, datetime
+
+
+class Offer(models.Model):
+ author = models.CharField(_('author'), max_length=255)
+ title = models.CharField(_('title'), max_length=255)
+ slug = models.SlugField(_('slug'))
+ book_url = models.URLField(_('book URL'), blank=True)
+ redakcja_url = models.URLField(_('redakcja URL'), blank=True)
+ target = models.DecimalField(_('target'), decimal_places=2, max_digits=10)
+ start = models.DateField(_('start'))
+ end = models.DateField(_('end'))
+
+ class Meta:
+ verbose_name = _('offer')
+ verbose_name_plural = _('offers')
+ ordering = ['-end']
+
+ def __unicode__(self):
+ return u"%s – %s" % (self.author, self.title)
+
+ def get_absolute_url(self):
+ return reverse('funding_offer', args=[self.slug])
+
+ @classmethod
+ def current(cls):
+ today = date.today()
+ objects = cls.objects.filter(start__lte=today, end__gte=today)
+ try:
+ return objects[0]
+ except IndexError:
+ return None
+
+ @classmethod
+ def public(cls):
+ today = date.today()
+ return cls.objects.filter(start__lte=today)
+
+ def get_perks(self, amount=None):
+ perks = Perk.objects.filter(
+ models.Q(offer=self) | models.Q(offer=None)
+ )
+ if amount is not None:
+ perks = perks.filter(price__lte=amount)
+ return perks
+
+ def fund(self, name, email, amount):
+ funding = self.funding_set.create(
+ name=name, email=email, amount=amount,
+ payed_at=datetime.now())
+ funding.perks = self.get_perks(amount)
+ return funding
+
+ def sum(self):
+ return self.funding_set.aggregate(s=models.Sum('amount'))['s'] or 0
+
+ def state(self):
+ if self.sum() >= self.target:
+ return 'win'
+ elif self.start <= date.today() <= self.end:
+ return 'running'
+ else:
+ return 'lose'
+
+
+class Perk(models.Model):
+ offer = models.ForeignKey(Offer, verbose_name=_('offer'), null=True, blank=True)
+ price = models.DecimalField(_('price'), decimal_places=2, max_digits=10)
+ name = models.CharField(_('name'), max_length=255)
+ description = models.TextField(_('description'), blank=True)
+
+ class Meta:
+ verbose_name = _('perk')
+ verbose_name_plural = _('perks')
+ ordering = ['-price']
+
+ def __unicode__(self):
+ return "%s (%s%s)" % (self.name, self.price, u" for %s" % self.offer if self.offer else "")
+
+
+class Funding(models.Model):
+ offer = models.ForeignKey(Offer, verbose_name=_('offer'))
+ name = models.CharField(_('name'), max_length=127)
+ email = models.EmailField(_('email'))
+ amount = models.DecimalField(_('amount'), decimal_places=2, max_digits=10)
+ payed_at = models.DateTimeField(_('payed_at'))
+ perks = models.ManyToManyField(Perk, verbose_name=_('perks'), blank=True)
+
+ class Meta:
+ verbose_name = _('funding')
+ verbose_name_plural = _('fundings')
+ ordering = ['-payed_at']
+
+ def __unicode__(self):
+ return "%s payed %s for %s" % (self.name, self.amount, self.offer)
--- /dev/null
+.standalone-funding {
+ border: 1px solid black; }
+
+.funding {
+ font-size: 1.5em;
+ padding: 0;
+ padding: .5em 1em;
+ background-image: url(/static/img/green-pixel.png);
+ background-repeat: repeat-y; }
+ .funding a {
+ color: black; }
+
+.funding-plus {
+ background: rgba(13, 126, 133, 0.2); }
+
+.funding-minus {
+ background: rgba(255, 0, 0, 0.2); }
--- /dev/null
+.standalone-funding {
+ border: 1px solid black;
+}
+
+.funding {
+ font-size: 1.5em;
+ padding: 0;
+ padding: .5em 1em;
+ background-image: url(/static/img/green-pixel.png);
+ background-repeat: repeat-y;
+
+ a {
+ color: black;
+ }
+}
+
+.funding-plus {
+ background: fade-out(#0D7E85, .8);
+}
+
+.funding-minus {
+ background: fade-out(red, .8);
+}
--- /dev/null
+{% extends "base.html" %}
+{% load funding_tags %}
+
+{% block titleextra %}{{ object }}{% endblock %}
+
+{% block body %}
+
+<h1>{{ object }}</h1>
+<div class="white-box normal-text">
+
+{% funding object add_class="standalone-funding" %}
+
+{% if object.state == 'running' %}
+
+
+<h3>Wpłać:</h3>
+<form>
+ {% for perk in object.get_perks %}
+ <br/><label><input type="radio" name="amount" value="{{ perk.price }}" /> {{perk.price}} zł: {{perk.name}}</label>
+ {% endfor %}
+ <br/><label><input type="radio" name="amount" value="" /> Dowolną kwotę:</label>
+ <input name="amount" size="5" /> zł
+ </label>
+
+
+ <br/><button type="submit">Wpłać!</button>
+</form>
+{% endif %}
+</div>
+
+
+<h2 style="font-size: 3em; margin: 1em 0 .5em;">Wpłaty:</h2>
+
+<div class="white-box normal-text">
+
+
+<ul>
+{% for funding in object.funding_set.all %}
+ <li>{{ funding.name }}: <strong>{{ funding.amount }} zł</strong></li>
+{% endfor %}
+</ul>
+</div>
+
+{% endblock %}
--- /dev/null
+{% extends "base.html" %}
+{% load funding_tags %}
+
+{% block titleextra %}Funding{% endblock %}
+
+{% block body %}
+<h1>Funding!</h1>
+
+<ul class="plain">
+{% for funding in object_list %}
+ <li class="white-box normal-text" style="margin-bottom:1em;">
+ <p>{{ funding.start }} – {{ funding.end }}</p>
+ {% funding funding link=1 add_class="standalone-funding" %}
+ {% if funding.state == "win" %}
+ {% if funding.book_url %}
+ <p><a href="{{ funding.book_url }}">Zobacz opublikowaną książkę</a></p>
+ {% else %}
+ <p>Termin publikacji: {{ funding.term }}</p>
+ {% endif %}
+ {% elif funding.state == "lose" %}
+ <p>Cel finansowania nie został osiągnięty. Zgromadzone środki
+ zostały przekazane na Fundusz Wolnych Lektur.</p>
+ {% endif %}
+ </li>
+{% endfor %}
+</ul>
+
+{% endblock %}
--- /dev/null
+{% load time_tags %}
+<div
+ class="funding {{ add_class }}"
+ style="background-size: {{ percentage|stringformat:'.2f' }}% 1px;">
+ {% if link %}<a href="{{ offer.get_absolute_url }}">{% endif %}
+ {{ offer }};
+ zebraliśmy {{ offer.sum }} z {{ offer.target }}{% if offer.state = 'running' %};
+ do końca: <strong class="countdown inline" data-until='{{ offer.end|local_to_utc|utc_for_js }}'></strong>
+ {% endif %}
+ {% if link %}</a>{% endif %}
+</div>
--- /dev/null
+{% extends "base.html" %}
+
+{% block titleextra %}Fundusz Wolnych Lektur{% endblock %}
+
+{% block body %}
+
+<h1>Fundusz Wolnych Lektur</h1>
+<p>Suma: {{ amount }}</p>
+
+<ul class="plain">
+ {% for entry in log %}
+ <li class="white-box normal-text funding-plus">
+ {{ entry.end }} +{{ entry.sum }}
+ <span style="float:right">Częściowa zbiórka na: {{ entry }}</span>
+ </li>
+ {% endfor %}
+</ul>
+
+
+{% endblock %}
--- /dev/null
+from django import template
+from ..models import Offer
+
+register = template.Library()
+
+
+@register.inclusion_tag("funding/tags/funding.html")
+def funding(offer=None, link=False, add_class=""):
+ if offer is None:
+ offer = Offer.current()
+
+ return {
+ 'offer': offer,
+ 'percentage': 100 * offer.sum() / offer.target,
+ 'link': link,
+ 'add_class': add_class,
+ }
--- /dev/null
+# -*- coding: utf-8 -*-
+# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+from django.test import TestCase
+from .models import Offer, Perk, Funding
+
+
+class FundTest(TestCase):
+ def setUp(self):
+ self.offer1 = Offer.objects.create(
+ author='author1', title='title1', slug='slug1',
+ target=100, start='2013-03-01', end='2013-03-31')
+
+ def test_perks(self):
+ perk = Perk.objects.create(price=20, name='Perk 20')
+ perk1 = Perk.objects.create(offer=self.offer1, price=50, name='Perk 50')
+ offer2 = Offer.objects.create(
+ author='author2', title='title2', slug='slug2',
+ target=100, start='2013-02-01', end='2013-02-20')
+ perk2 = Perk.objects.create(offer=offer2, price=1, name='Perk 1')
+
+ self.assertEqual(
+ set(self.offer1.fund('Tester', 'test@example.com', 10).perks.all()),
+ set())
+ self.assertEqual(
+ set(self.offer1.fund('Tester', 'test@example.com', 70).perks.all()),
+ set([perk, perk1]))
--- /dev/null
+# -*- coding: utf-8 -*-
+# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+from django.conf.urls import patterns, url
+from django.views.generic import DetailView, ListView, FormView
+
+from .models import Offer
+from .views import WLFundView
+
+
+urlpatterns = patterns('',
+ url(r'^$', ListView.as_view(queryset=Offer.public()), name='funding'),
+ url(r'lektura/^(?P<slug>[^/]+)/$', DetailView.as_view(queryset=Offer.public()), name='funding_offer'),
+ url(r'fundusz/$', WLFundView.as_view(), name='funding_wlfund'),
+)
--- /dev/null
+# Create your views here.
+from django.views.generic import TemplateView
+from .models import Offer
+
+
+def mix(*streams):
+ substreams = []
+ for stream, read_date in streams:
+ iterstream = iter(stream)
+ try:
+ item = next(iterstream)
+ except StopIteration:
+ pass
+ else:
+ substreams.append([read_date(item), item, iterstream, read_date])
+ while substreams:
+ i, substream = max(enumerate(substreams), key=lambda x: x[0])
+ yield substream[1]
+ try:
+ item = next(substream[2])
+ except StopIteration:
+ del substreams[i]
+ else:
+ substream[0:2] = [substream[3](item), item]
+
+
+class WLFundView(TemplateView):
+ template_name = "funding/wlfund.html"
+
+ def get_context_data(self):
+ ctx = super(WLFundView, self).get_context_data()
+ offers = [o for o in Offer.objects.all() if o.state() == 'lose' and o.sum()]
+ amount = sum(o.sum() for o in offers)
+ print offers
+
+ #offers = (o for o in Offer.objects.all() if o.state() == 'lose' and o.sum())
+ ctx['amount'] = amount
+ ctx['log'] = mix((offers, lambda x: x.end))
+ return ctx
from django.db.models import Q
from django.conf import settings
from django.contrib.sites.models import Site
+from django.utils import timezone
WL_DC_READER_XPATH = '(.|*)/rdf:RDF/rdf:Description/%s/text()'
self.oai_id = "oai:" + Site.objects.get_current().domain + ":%s"
# earliest change
- year_zero = datetime(1990, 1, 1, 0, 0, 0)
+ year_zero = timezone.make_aware(datetime(1990, 1, 1, 0, 0, 0), timezone.utc)
try:
earliest_change = \
(function($) {
$(function() {
-
- $('#countdown').each(function() {
+ $('.countdown').each(function() {
var $this = $(this);
var serverTime = function() {
$.countdown.setDefaults($.countdown.regional['']);
}
- var d = new Date($this.attr('data-year'), 0, 1);
- function re() {location.reload()};
- $this.countdown({until: d, format: 'ydHMS', serverSync: serverTime,
- onExpiry: re, alwaysExpire: true});
-
+ var options = {
+ until: new Date($this.attr('data-until')),
+ format: 'ydHMS',
+ serverSync: serverTime,
+ onExpiry: function(){location.reload()}, // TODO: no reload
+ };
+ if ($this.hasClass('inline')) {
+ options.layout = '{dn} {dl} {hnn}{sep}{mnn}{sep}{snn}';
+ }
+
+ $this.countdown(options);
});
});
-})(jQuery);
\ No newline at end of file
+})(jQuery);
{% extends "base.html" %}
{% load i18n %}
+{% load time_tags %}
{% block titleextra %}{{ author.name }}{% endblock %}
{% else %}
<div>
<p>{% trans "This author's works will become part of public domain and will be allowed to be published without restrictions in" %}</p>
- <div id='countdown' data-year='{{ pd_counter }}'></div>
+ <div class='countdown' data-until='{{ pd_counter|local_to_utc|utc_for_js }}'></div>
<p>{% trans "<a href='http://domenapubliczna.org/co-to-jest-domena-publiczna/'>Find out</a> why Internet libraries can't publish this author's works." %}</p>
</div>
{% endif %}
{% extends "base.html" %}
{% load i18n %}
+{% load time_tags %}
{% block titleextra %}{{ book.title }}{% endblock %}
{% else %}
{% if book.pd %}
<p>{% trans "This work will become part of public domain and will be allowed to be published without restrictions in" %}</p>
- <div id='countdown' data-year='{{ pd_counter }}'></div>
+ <div class='countdown' data-until='{{ pd_counter|local_to_utc|utc_for_js }}'></div>
<p>{% trans "<a href='http://domenapubliczna.org/co-to-jest-domena-publiczna/'>Find out</a> why Internet libraries can't publish this work." %}</p>
{% else %}
<p>{% trans "This work is copyrighted." %}
--- /dev/null
+import datetime
+import pytz
+from django.conf import settings
+from django import template
+from django.utils import timezone
+
+
+register = template.Library()
+
+@register.filter
+def local_to_utc(localtime):
+ if isinstance(localtime, datetime.date):
+ localtime = datetime.datetime.combine(localtime, datetime.time(0,0))
+ return timezone.utc.normalize(
+ pytz.timezone(settings.TIME_ZONE).localize(localtime)
+ )
+
+
+@register.filter
+def utc_for_js(dt):
+ return dt.strftime('%Y/%m/%d %H:%M:%S UTC')
# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
#
-#from datetime import datetime
-
+from datetime import datetime
from django.template import RequestContext
from django.shortcuts import render_to_response, get_object_or_404
from pdcounter import models
def book_stub_detail(request, slug):
book = get_object_or_404(models.BookStub, slug=slug)
- pd_counter = book.pd
+ pd_counter = datetime(book.pd, 1, 1)
form = PublishingSuggestForm(
initial={"books": u"%s — %s, \n" % (book.author, book.title)})
def author_detail(request, slug):
author = get_object_or_404(models.Author, slug=slug)
- pd_counter = author.goes_to_pd()
+ pd_counter = datetime(author.goes_to_pd(), 1, 1)
form = PublishingSuggestForm(initial={"books": author.name + ", \n"})
color: #989898;
}
+#logo a, #logo img {
+ display: block;
+}
#tagline {
margin-left: 1.5em;
<html lang="{{ LANGUAGE_CODE }}" prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb#">
{% load cache compressed i18n %}
{% load static from staticfiles %}
- {% load catalogue_tags reporting_stats sponsor_tags %}
+ {% load catalogue_tags funding_tags reporting_stats sponsor_tags %}
<head>
<meta charset="utf-8">
<meta name="application-name" content="Wolne Lektury" />
</div>
-
<div id="main-content">
-
+ {% funding link=1 %}
<div id="nav-line">
{% catalogue_menu %}
def clock(request):
- """ Provides server time for jquery.countdown,
+ """ Provides server UTC time for jquery.countdown,
in a format suitable for Date.parse()
"""
- return HttpResponse(datetime.now().strftime('%Y/%m/%d %H:%M:%S'))
+ return HttpResponse(datetime.utcnow().strftime('%Y/%m/%d %H:%M:%S UTC'))
def publish_plan(request):
+-i http://pypi.nowoczesnapolska.org.pl/simple/
+
django-debug-toolbar
polib
BabelDjango
+-i http://pypi.nowoczesnapolska.org.pl/simple/
+
nose>=0.11
django-nose
nosexcover
+-i http://pypi.nowoczesnapolska.org.pl/simple/
--find-links=http://www.pythonware.com/products/pil/
# django
Django>=1.4,<1.5
South>=0.7 # migrations for django
-django-pipeline>=1.2
+django-pipeline>=1.2.24,<1.3
django-pagination>=1.0
django-maintenancemode>=0.10
django-piston==0.2.2
#django-allauth>=0.4,<0.5
# version of django-allauth 0.4 with install script fixed
-e git+git://github.com/pennersr/django-allauth.git@3a03db9b2ecca370af228df367bd8fa52afea5ea#egg=django-allauth
+pytz
django-honeypot
django-uni-form
'waiter',
'search',
'oai',
+ 'funding',
]
INSTALLED_APPS_CONTRIB = [
# If running in a Windows environment this must be set to the same as your
# system time zone.
TIME_ZONE = 'Europe/Warsaw'
+USE_TZ = True
SITE_ID = 1
# If you set this to False, Django will make some optimizations so as not
# to load the internationalization machinery.
USE_I18N = True
+USE_L10N = True
LOCALE_PATHS = [
path.join(PROJECT_DIR, 'locale-contrib')
'css/catalogue.css',
'sponsors/css/sponsors.css',
'css/auth.css',
+ 'funding/funding.scss',
'css/social/shelf_tags.css',
'css/ui-lightness/jquery-ui-1.8.16.custom.css',
STATICFILES_STORAGE = 'pipeline.storage.PipelineCachedStorage'
PIPELINE_CSS_COMPRESSOR = None
PIPELINE_JS_COMPRESSOR = None
+
+PIPELINE_COMPILERS = (
+ 'pipeline.compilers.sass.SASSCompiler',
+)
url(r'^ludzie/', include('social.urls')),
url(r'^uzytkownik/', include('allauth.urls')),
url(r'^czekaj/', include('waiter.urls')),
+ url(r'^fund/', include('funding.urls')),
# Admin panel
url(r'^admin/catalogue/book/import$', 'catalogue.views.import_book', name='import_book'),
--- /dev/null
+import pytz
+from django.utils import timezone
+from django.conf import settings
+
+def localtime_to_utc(localtime):
+ return timezone.utc.normalize(
+ pytz.timezone(settings.TIME_ZONE).localize(localtime)
+ )
+
+def utc_for_js(dt):
+ return dt.strftime('%Y/%m/%d %H:%M:%S UTC')