diff --git a/src/backoffice/urls.py b/src/backoffice/urls.py index 7c849a55..e1af8680 100644 --- a/src/backoffice/urls.py +++ b/src/backoffice/urls.py @@ -1,6 +1,9 @@ from django.conf.urls import url from .views import * + +app_name = 'backoffice' + urlpatterns = [ url(r'^$', BackofficeIndexView.as_view(), name='index'), url(r'product_handout/$', ProductHandoutView.as_view(), name='product_handout'), diff --git a/src/bornhack/environment_settings.py.dist b/src/bornhack/environment_settings.py.dist index 191c3d5e..042ce179 100644 --- a/src/bornhack/environment_settings.py.dist +++ b/src/bornhack/environment_settings.py.dist @@ -24,11 +24,12 @@ WKHTMLTOPDF_CMD="{{ wkhtmltopdf_path }}" CHANNEL_LAYERS = { "default": { "BACKEND": "{{ django_channels_backend }}", - "ROUTING": "bornhack.routing.channel_routing", "CONFIG": {{ django_channels_config }} }, } +ASGI_APPLICATION = "bornhack.routing.application" + # start redirecting to the next camp instead of the previous camp after # this much of the time between the camps has passed CAMP_REDIRECT_PERCENT=25 diff --git a/src/bornhack/routing.py b/src/bornhack/routing.py index 7776c0e7..d426b02c 100644 --- a/src/bornhack/routing.py +++ b/src/bornhack/routing.py @@ -1,8 +1,14 @@ +from django.conf.urls import url + +from channels.routing import ProtocolTypeRouter, URLRouter +from channels.auth import AuthMiddlewareStack from program.consumers import ScheduleConsumer -channel_routing = [ - ScheduleConsumer.as_route(path=r"^/schedule/"), -] - - +application = ProtocolTypeRouter({ + "websocket": AuthMiddlewareStack( + URLRouter([ + url(r"^schedule/", ScheduleConsumer) + ]) + ) +}) diff --git a/src/bornhack/urls.py b/src/bornhack/urls.py index 5d1189d9..e50c504a 100644 --- a/src/bornhack/urls.py +++ b/src/bornhack/urls.py @@ -62,7 +62,7 @@ urlpatterns = [ name='general-terms' ), url(r'^accounts/', include('allauth.urls')), - url(r'^admin/', include(admin.site.urls)), + url(r'^admin/', admin.site.urls), url( r'^camps/$', diff --git a/src/camps/migrations/0001_initial.py b/src/camps/migrations/0001_initial.py index 167f4b26..f715b265 100644 --- a/src/camps/migrations/0001_initial.py +++ b/src/camps/migrations/0001_initial.py @@ -35,7 +35,7 @@ class Migration(migrations.Migration): ('updated', models.DateTimeField(auto_now=True)), ('uuid', models.UUIDField(default=uuid.uuid4, serialize=False, editable=False, primary_key=True)), ('date', models.DateField(help_text='What date?', verbose_name='Date')), - ('camp', models.ForeignKey(to='camps.Camp', help_text='Which camp does this day belong to.', verbose_name='Camp')), + ('camp', models.ForeignKey(on_delete=models.PROTECT, to='camps.Camp', help_text='Which camp does this day belong to.', verbose_name='Camp')), ], options={ 'verbose_name_plural': 'Days', @@ -51,8 +51,8 @@ class Migration(migrations.Migration): ('description', models.CharField(max_length=255, help_text='What this expense covers.', verbose_name='Description')), ('amount', models.DecimalField(max_digits=7, help_text='The amount of the expense.', verbose_name='Amount', decimal_places=2)), ('currency', models.CharField(max_length=3, choices=[('btc', 'BTC'), ('dkk', 'DKK'), ('eur', 'EUR'), ('sek', 'SEK')], help_text='What currency the amount is in.', verbose_name='Currency')), - ('camp', models.ForeignKey(to='camps.Camp', help_text='The camp to which this expense relates to.', verbose_name='Camp')), - ('covered_by', models.ForeignKey(to=settings.AUTH_USER_MODEL, blank=True, help_text='Which user, if any, covered this expense.', verbose_name='Covered by', null=True)), + ('camp', models.ForeignKey(on_delete=models.PROTECT, to='camps.Camp', help_text='The camp to which this expense relates to.', verbose_name='Camp')), + ('covered_by', models.ForeignKey(on_delete=models.PROTECT, to=settings.AUTH_USER_MODEL, blank=True, help_text='Which user, if any, covered this expense.', verbose_name='Covered by', null=True)), ], options={ 'verbose_name_plural': 'Expenses', @@ -67,8 +67,8 @@ class Migration(migrations.Migration): ('uuid', models.UUIDField(default=uuid.uuid4, serialize=False, editable=False, primary_key=True)), ('cost', models.DecimalField(default=1500.0, decimal_places=2, help_text='What the user should/is willing to pay for this signup.', verbose_name='Cost', max_digits=7)), ('paid', models.BooleanField(help_text='Whether the user has paid.', verbose_name='Paid?', default=False)), - ('camp', models.ForeignKey(to='camps.Camp', help_text='The camp that has been signed up for.', verbose_name='Camp')), - ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL, help_text='The user that has signed up.', verbose_name='User')), + ('camp', models.ForeignKey(on_delete=models.PROTECT, to='camps.Camp', help_text='The camp that has been signed up for.', verbose_name='Camp')), + ('user', models.ForeignKey(on_delete=models.PROTECT, to=settings.AUTH_USER_MODEL, help_text='The user that has signed up.', verbose_name='User')), ], options={ 'verbose_name_plural': 'Signups', diff --git a/src/news/urls.py b/src/news/urls.py index 766b4e3c..6f8d3818 100644 --- a/src/news/urls.py +++ b/src/news/urls.py @@ -1,7 +1,7 @@ from django.conf.urls import url from . import views - +app_name = 'news' urlpatterns = [ url(r'^$', views.NewsIndex.as_view(), kwargs={'archived': False}, name='index'), url(r'^archive/$', views.NewsIndex.as_view(), kwargs={'archived': True}, name='archive'), diff --git a/src/profiles/migrations/0001_initial.py b/src/profiles/migrations/0001_initial.py index 95aa9c79..e4617e2c 100644 --- a/src/profiles/migrations/0001_initial.py +++ b/src/profiles/migrations/0001_initial.py @@ -19,7 +19,7 @@ class Migration(migrations.Migration): ('created', models.DateTimeField(auto_now_add=True)), ('updated', models.DateTimeField(auto_now=True)), ('uuid', models.UUIDField(serialize=False, editable=False, primary_key=True, default=uuid.uuid4)), - ('user', models.OneToOneField(to=settings.AUTH_USER_MODEL, help_text='The django user this profile belongs to.', verbose_name='User')), + ('user', models.OneToOneField(on_delete=models.PROTECT, to=settings.AUTH_USER_MODEL, help_text='The django user this profile belongs to.', verbose_name='User')), ], options={ 'verbose_name_plural': 'Profiles', diff --git a/src/profiles/models.py b/src/profiles/models.py index 1d95e769..986ea120 100644 --- a/src/profiles/models.py +++ b/src/profiles/models.py @@ -19,6 +19,7 @@ class Profile(CreatedUpdatedModel, UUIDModel): User, verbose_name=_('User'), help_text=_('The django user this profile belongs to.'), + on_delete=models.PROTECT ) name = models.CharField( diff --git a/src/profiles/urls.py b/src/profiles/urls.py index 54c8123c..8c3f5b07 100644 --- a/src/profiles/urls.py +++ b/src/profiles/urls.py @@ -2,6 +2,8 @@ from django.conf.urls import url from .views import ProfileDetail, ProfileUpdate + +app_name = 'profiles' urlpatterns = [ url(r'^$', ProfileDetail.as_view(), name='detail'), url(r'^edit$', ProfileUpdate.as_view(), name='update'), diff --git a/src/profiles/views.py b/src/profiles/views.py index 4c991aff..4ac9a02a 100644 --- a/src/profiles/views.py +++ b/src/profiles/views.py @@ -1,6 +1,6 @@ from django.contrib.auth.mixins import LoginRequiredMixin from django.views.generic import DetailView, UpdateView -from django.core.urlresolvers import reverse_lazy +from django.urls import reverse_lazy from django.contrib import messages from . import models diff --git a/src/program/consumers.py b/src/program/consumers.py index 06713545..d5ac9dc8 100644 --- a/src/program/consumers.py +++ b/src/program/consumers.py @@ -1,4 +1,4 @@ -from channels.generic.websockets import JsonWebsocketConsumer +from channels.generic.websocket import JsonWebsocketConsumer from camps.models import Camp from .models import ( @@ -12,13 +12,11 @@ from .models import ( class ScheduleConsumer(JsonWebsocketConsumer): - http_user = True + groups = ['schedule_users'] - def connection_groups(self, **kwargs): - return ['schedule_users'] - - def raw_receive(self, message, **kwargs): - content = self.decode_json(message['text']) + def receive(self, text_data, **kwargs): + user = self.scope['user'] + content = self.decode_json(text_data) action = content.get('action') data = {} @@ -39,14 +37,27 @@ class ScheduleConsumer(JsonWebsocketConsumer): events_query_set = Event.objects.filter(camp=camp) events = list([x.serialize() for x in events_query_set]) - event_instances_query_set = EventInstance.objects.filter(event__camp=camp) - event_instances = list([x.serialize(user=message.user) for x in event_instances_query_set]) + event_instances_query_set = EventInstance.objects.filter( + event__camp=camp + ) + event_instances = list([ + x.serialize(user=user) + for x in event_instances_query_set + ]) - event_locations_query_set = EventLocation.objects.filter(camp=camp) - event_locations = list([x.serialize() for x in event_locations_query_set]) + event_locations_query_set = EventLocation.objects.filter( + camp=camp + ) + event_locations = list([ + x.serialize() + for x in event_locations_query_set + ]) event_types_query_set = EventType.objects.filter() - event_types = list([x.serialize() for x in event_types_query_set]) + event_types = list([ + x.serialize() + for x in event_types_query_set + ]) speakers_query_set = Speaker.objects.filter(camp=camp) speakers = list([x.serialize() for x in speakers_query_set]) @@ -67,18 +78,21 @@ class ScheduleConsumer(JsonWebsocketConsumer): event_instance_id = content.get('event_instance_id') event_instance = EventInstance.objects.get(id=event_instance_id) Favorite.objects.create( - user=message.user, + user=user, event_instance=event_instance ) if action == 'unfavorite': event_instance_id = content.get('event_instance_id') event_instance = EventInstance.objects.get(id=event_instance_id) - favorite = Favorite.objects.get(event_instance=event_instance, user=message.user) + favorite = Favorite.objects.get( + event_instance=event_instance, + user=user + ) favorite.delete() if data: - self.send(data) + self.send_json(data) def disconnect(self, message, **kwargs): pass diff --git a/src/program/models.py b/src/program/models.py index c7e7b34b..04503f5b 100644 --- a/src/program/models.py +++ b/src/program/models.py @@ -11,7 +11,7 @@ from django.db import models from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.utils.text import slugify from django.conf import settings -from django.core.urlresolvers import reverse_lazy +from django.urls import reverse_lazy from django.core.files.storage import FileSystemStorage from django.urls import reverse from django.apps import apps @@ -75,6 +75,7 @@ class UserSubmittedModel(CampRelatedModel): user = models.ForeignKey( 'auth.User', + on_delete=models.PROTECT ) PROPOSAL_DRAFT = 'draft' @@ -148,7 +149,8 @@ class SpeakerProposal(UserSubmittedModel): camp = models.ForeignKey( 'camps.Camp', - related_name='speakerproposals' + related_name='speakerproposals', + on_delete=models.PROTECT ) name = models.CharField( @@ -216,7 +218,8 @@ class EventProposal(UserSubmittedModel): camp = models.ForeignKey( 'camps.Camp', - related_name='eventproposals' + related_name='eventproposals', + on_delete=models.PROTECT ) title = models.CharField( @@ -231,6 +234,7 @@ class EventProposal(UserSubmittedModel): event_type = models.ForeignKey( 'program.EventType', help_text='The type of event', + on_delete=models.PROTECT ) speakers = models.ManyToManyField( @@ -300,7 +304,8 @@ class EventLocation(CampRelatedModel): camp = models.ForeignKey( 'camps.Camp', - related_name='eventlocations' + related_name='eventlocations', + on_delete=models.PROTECT ) def __str__(self): @@ -379,6 +384,7 @@ class Event(CampRelatedModel): event_type = models.ForeignKey( 'program.EventType', help_text='The type of this event', + on_delete=models.PROTECT ) slug = models.SlugField( @@ -391,6 +397,7 @@ class Event(CampRelatedModel): 'camps.Camp', related_name='events', help_text='The camp this event belongs to', + on_delete=models.PROTECT ) video_url = models.URLField( @@ -410,6 +417,7 @@ class Event(CampRelatedModel): null=True, blank=True, help_text='The event proposal object this event was created from', + on_delete=models.PROTECT ) class Meta: @@ -463,7 +471,8 @@ class EventInstance(CampRelatedModel): event = models.ForeignKey( 'program.event', - related_name='instances' + related_name='instances', + on_delete=models.PROTECT ) when = DateTimeRangeField() @@ -474,7 +483,8 @@ class EventInstance(CampRelatedModel): location = models.ForeignKey( 'program.EventLocation', - related_name='eventinstances' + related_name='eventinstances', + on_delete=models.PROTECT ) class Meta: @@ -600,6 +610,7 @@ class Speaker(CampRelatedModel): null=True, related_name='speakers', help_text='The camp this speaker belongs to', + on_delete=models.PROTECT ) events = models.ManyToManyField( @@ -614,6 +625,7 @@ class Speaker(CampRelatedModel): null=True, blank=True, help_text='The speaker proposal object this speaker was created from', + on_delete=models.PROTECT ) class Meta: @@ -660,7 +672,10 @@ class Favorite(models.Model): related_name='favorites', on_delete=models.PROTECT ) - event_instance = models.ForeignKey('program.EventInstance') + event_instance = models.ForeignKey( + 'program.EventInstance', + on_delete=models.PROTECT + ) class Meta: unique_together = ['user', 'event_instance'] diff --git a/src/requirements/dev.txt b/src/requirements/dev.txt new file mode 100644 index 00000000..da53e734 --- /dev/null +++ b/src/requirements/dev.txt @@ -0,0 +1,3 @@ +-r production.txt + +django-debug-toolbar==1.9.1 diff --git a/src/requirements.txt b/src/requirements/production.txt similarity index 63% rename from src/requirements.txt rename to src/requirements/production.txt index 5c860590..c675c1d2 100644 --- a/src/requirements.txt +++ b/src/requirements/production.txt @@ -1,26 +1,19 @@ +Django==2.0.4 +channels==2.0.2 +channels_redis=2.1.1 + CommonMark==0.7.3 -Django==1.10.5 Pillow==4.0.0 PyPDF2==1.26.0 Unidecode==0.04.20 argparse==1.2.1 -asgi-redis==1.3.0 asyncio==3.4.3 bleach==1.5.0 -# fix https://github.com/django/daphne/pull/107 -git+https://github.com/tykling/daphne@ws-x-forwarded-for-bugfix - -# fix https://github.com/django/channels/issues/622 -#channels==1.1.3 -git+https://github.com/tykling/channels@master - defusedxml==0.4.1 django-allauth==0.30.0 django-bleach==0.3.0 django-bootstrap3==8.2.2 -django-debug-toolbar==1.6 -django-channels-panel==0.0.5 django-extensions==1.7.7 django-wkhtmltopdf==3.1.0 docopt==0.6.2 diff --git a/src/shop/context_processors.py b/src/shop/context_processors.py index 94796c89..ec572d80 100644 --- a/src/shop/context_processors.py +++ b/src/shop/context_processors.py @@ -1,5 +1,5 @@ def current_order(request): - if request.user.is_authenticated(): + if request.user.is_authenticated: order = None orders = request.user.orders.filter(open__isnull=False) diff --git a/src/shop/models.py b/src/shop/models.py index 3ec7c917..9ea27135 100644 --- a/src/shop/models.py +++ b/src/shop/models.py @@ -8,7 +8,7 @@ from django.contrib.postgres.fields import DateTimeRangeField, JSONField from django.utils.text import slugify from django.utils.translation import ugettext_lazy as _ from django.utils import timezone -from django.core.urlresolvers import reverse_lazy +from django.urls import reverse_lazy from django.core.exceptions import ValidationError from decimal import Decimal from datetime import timedelta @@ -376,7 +376,7 @@ class EpayPayment(CreatedUpdatedModel, UUIDModel): verbose_name = 'Epay Payment' verbose_name_plural = 'Epay Payments' - order = models.OneToOneField('shop.Order') + order = models.OneToOneField('shop.Order', on_delete=models.PROTECT) callback = models.ForeignKey('shop.EpayCallback', on_delete=models.PROTECT) txnid = models.IntegerField() @@ -469,8 +469,18 @@ class CreditNote(CreatedUpdatedModel): class Invoice(CreatedUpdatedModel): - order = models.OneToOneField('shop.Order', null=True, blank=True) - customorder = models.OneToOneField('shop.CustomOrder', null=True, blank=True) + order = models.OneToOneField( + 'shop.Order', + null=True, + blank=True, + on_delete=models.PROTECT + ) + customorder = models.OneToOneField( + 'shop.CustomOrder', + null=True, + blank=True, + on_delete=models.PROTECT + ) pdf = models.FileField(null=True, blank=True, upload_to='invoices/') sent_to_customer = models.BooleanField(default=False) diff --git a/src/shop/urls.py b/src/shop/urls.py index 5f089d28..f3ea70c7 100644 --- a/src/shop/urls.py +++ b/src/shop/urls.py @@ -1,6 +1,8 @@ from django.conf.urls import url from .views import * +app_name = 'shop' + urlpatterns = [ url(r'^$', ShopIndexView.as_view(), name='index'), diff --git a/src/shop/views.py b/src/shop/views.py index 2ed4dc13..025f62f3 100644 --- a/src/shop/views.py +++ b/src/shop/views.py @@ -1,7 +1,7 @@ from django.conf import settings from django.contrib import messages from django.contrib.auth.mixins import LoginRequiredMixin -from django.core.urlresolvers import reverse, reverse_lazy +from django.urls import reverse, reverse_lazy from django.db.models import Count, F from django.http import ( HttpResponse, diff --git a/src/teams/models.py b/src/teams/models.py index 94affcff..03b8968b 100644 --- a/src/teams/models.py +++ b/src/teams/models.py @@ -5,7 +5,7 @@ from django.utils.text import slugify from utils.models import CampRelatedModel from django.core.exceptions import ValidationError from django.contrib.auth.models import User -from django.core.urlresolvers import reverse_lazy +from django.urls import reverse_lazy from django.conf import settings import logging logger = logging.getLogger("bornhack.%s" % __name__) diff --git a/src/teams/urls.py b/src/teams/urls.py index 42de9a28..eecc5acc 100644 --- a/src/teams/urls.py +++ b/src/teams/urls.py @@ -1,6 +1,9 @@ from django.conf.urls import url, include from .views import * + +app_name = 'teams' + urlpatterns = [ url( r'^$', diff --git a/src/teams/views.py b/src/teams/views.py index e893a314..f47b33c7 100644 --- a/src/teams/views.py +++ b/src/teams/views.py @@ -8,9 +8,8 @@ from django.shortcuts import redirect from django.contrib import messages from django.http import HttpResponseRedirect from django.views.generic.detail import SingleObjectMixin -from django.core.urlresolvers import reverse_lazy +from django.urls import reverse_lazy from django.conf import settings - from profiles.models import Profile import logging diff --git a/src/tickets/models.py b/src/tickets/models.py index 31269e3b..8295c4ae 100644 --- a/src/tickets/models.py +++ b/src/tickets/models.py @@ -3,7 +3,7 @@ import hashlib import base64 import qrcode from django.conf import settings -from django.core.urlresolvers import reverse_lazy +from django.urls import reverse_lazy from django.utils.translation import ugettext_lazy as _ from utils.models import ( UUIDModel, diff --git a/src/tickets/urls.py b/src/tickets/urls.py index 002283ba..9ef1ffbe 100644 --- a/src/tickets/urls.py +++ b/src/tickets/urls.py @@ -6,6 +6,8 @@ from .views import ( ShopTicketDetailView ) +app_name = 'tickets' + urlpatterns = [ url( r'^$', diff --git a/src/villages/models.py b/src/villages/models.py index 24b7685b..4d8e8014 100644 --- a/src/villages/models.py +++ b/src/villages/models.py @@ -1,4 +1,4 @@ -from django.core.urlresolvers import reverse_lazy +from django.urls import reverse_lazy from django.db import models from django.utils.text import slugify from utils.models import UUIDModel, CampRelatedModel diff --git a/src/villages/urls.py b/src/villages/urls.py index 015818df..cf06449b 100644 --- a/src/villages/urls.py +++ b/src/villages/urls.py @@ -1,6 +1,9 @@ from django.conf.urls import url from .views import * + +app_name = 'villages' + urlpatterns = [ url(r'^$', VillageListView.as_view(), name='list'), url(r'create/$', VillageCreateView.as_view(), name='create'), diff --git a/src/villages/views.py b/src/villages/views.py index d7c42d83..482f7e0e 100644 --- a/src/villages/views.py +++ b/src/villages/views.py @@ -1,6 +1,6 @@ from django.http import Http404 from django.contrib.auth.mixins import LoginRequiredMixin -from django.core.urlresolvers import reverse_lazy +from django.urls import reverse_lazy from django.http import HttpResponseRedirect from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView from django.views.generic.detail import SingleObjectMixin