diff --git a/Makefile b/Makefile deleted file mode 100644 index b1f6a9fa..00000000 --- a/Makefile +++ /dev/null @@ -1,17 +0,0 @@ -SETTINGS = bornhack.settings.development - -all: migrate superuser run - -migrations: - ./dev.sh makemigrations --settings=$(SETTINGS) - -migrate: - ./dev.sh migrate --settings=$(SETTINGS) - -superuser: - ./dev.sh createsuperuser --settings=$(SETTINGS) - -run: - ./dev.sh runserver --settings=$(SETTINGS) - -.PHONY = all migrations migrate superuser run diff --git a/README.md b/README.md index 5d78b366..a503f09a 100644 --- a/README.md +++ b/README.md @@ -6,31 +6,50 @@ Django project to power Bornhack. Features include news, villages, webshop, and ### Virtualenv Create a Python 2.7 virtual environment and activate it: - $ virtualenv venv - $ source venv/bin/activate +``` +$ virtualenv venv +$ source venv/bin/activate +``` ### System libraries Install system dependencies (method depends on OS): -- postgresql headers (for psychopg2) +- postgresql headers (for psychopg2): + - Debian: ? + - FreeBSD: ? - libjpeg (for pdf generation) + - Debian: libjpeg-dev + - FreeBSD: ? ### Python packages -Install pip packages (pick either development or production): - (venv) $ pip install -r requirements/development.txt - (venv) $ pip install -r requirements/production.txt +Install pip packages: +``` + (venv) $ pip install -r requirements.txt +``` ### Configuration file -Copy environment file template and change settings: +Copy environment file template and change settings as needed: +``` (venv) $ cp bornhack/settings/env.dist bornhack/settings/.env +``` ### Database Is this a new installation? Initialize the database: - (venv) $ ./manage.py migrate --settings=bornhack.settings.development - (venv) $ ./manage.py createsuperuser --settings=bornhack.settings.development +``` + (venv) $ ./manage.py migrate +``` + +Is this for local development? Bootstrap the database with dummy data and users: +``` + (venv) $ ./manage.py bootstrap-devsite +``` ### Done Is this for local development? Start the Django devserver: - (venv) $ ./manage.py runserver --settings=bornhack.settings.development +``` + (venv) $ ./manage.py runserver +``` Otherwise start uwsgi or similar to serve the application. +Enjoy! + diff --git a/bornhack/settings/.env.dist b/bornhack/.env.dist similarity index 88% rename from bornhack/settings/.env.dist rename to bornhack/.env.dist index 7d025aed..f485fd14 100644 --- a/bornhack/settings/.env.dist +++ b/bornhack/.env.dist @@ -1,6 +1,7 @@ ### set database url and allowed_hosts here DATABASE_URL=postgres://username:password@host:port/database ALLOWED_HOSTS=localhost,127.0.0.1 +DEBUG=True ### changes below here are only needed for production SECRET_KEY=somethingverysecretandunique @@ -29,4 +30,7 @@ BANKACCOUNT_IBAN='123' BANKACCOUNT_SWIFTBIC='123' BANKACCOUNT_REG='123' BANKACCOUNT_ACCOUNT='123' +TIME_ZONE='Europe/Copenhagen' +SCHEDULE_MIDNIGHT_OFFSET_HOURS=6 +SCHEDULE_TIMESLOT_LENGTH_MINUTES=30 diff --git a/bornhack/settings/base.py b/bornhack/settings.py similarity index 66% rename from bornhack/settings/base.py rename to bornhack/settings.py index 40691913..7640f228 100644 --- a/bornhack/settings/base.py +++ b/bornhack/settings.py @@ -42,6 +42,8 @@ INSTALLED_APPS = [ 'utils', 'villages', 'program', + 'info', + 'sponsors', 'allauth', 'allauth.account', @@ -53,10 +55,14 @@ STATIC_ROOT = local_dir('static') STATICFILES_DIRS = [local_dir('static_src')] MEDIA_ROOT = env('MEDIA_ROOT') LANGUAGE_CODE = 'en-us' -TIME_ZONE = 'UTC' -USE_I18N = True -USE_L10N = True +TIME_ZONE = env('TIME_ZONE') +#USE_I18N = True +#USE_L10N = True USE_TZ = True +SHORT_DATE_FORMAT = 'd/m-Y' +DATE_FORMAT = 'd/m-Y' +DATETIME_FORMAT = 'd/m-Y H:i' +TIME_FORMAT = 'H:i' TEMPLATES = [ { @@ -69,9 +75,9 @@ TEMPLATES = [ 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', - 'camps.context_processors.current_camp', 'shop.context_processors.current_order', 'shop.context_processors.user_has_tickets', + 'camps.context_processors.camp', ], }, }, @@ -92,8 +98,8 @@ MIDDLEWARE_CLASSES = [ LOGIN_REDIRECT_URL = 'profiles:detail' AUTHENTICATION_BACKENDS = ( - 'django.contrib.auth.backends.ModelBackend', # Login to admin with username - 'allauth.account.auth_backends.AuthenticationBackend', + 'django.contrib.auth.backends.ModelBackend', # Handles login to admin with username + 'allauth.account.auth_backends.AuthenticationBackend', # Handles regular logins ) ACCOUNT_AUTHENTICATION_METHOD = 'email' @@ -127,3 +133,46 @@ BANKACCOUNT_REG = env('BANKACCOUNT_REG') BANKACCOUNT_ACCOUNT = env('BANKACCOUNT_ACCOUNT') TICKET_CATEGORY_ID = env('TICKET_CATEGORY_ID') + +DEBUG = env('DEBUG') +if DEBUG: + EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' + INSTALLED_APPS += ['debug_toolbar', ] + MIDDLEWARE_CLASSES += ['debug_toolbar.middleware.DebugToolbarMiddleware', ] + +else: + EMAIL_HOST = env('EMAIL_HOST') + EMAIL_PORT = env('EMAIL_PORT') + EMAIL_HOST_USER = env('EMAIL_HOST_USER') + EMAIL_HOST_PASSWORD = env('EMAIL_HOST_PASSWORD') + EMAIL_USE_TLS = env('EMAIL_USE_TLS') + DEFAULT_FROM_EMAIL = env('DEFAULT_FROM_EMAIL') + SERVER_EMAIL = env('DEFAULT_FROM_EMAIL') + ARCHIVE_EMAIL = env('ARCHIVE_EMAIL') + +LOGGING = { + 'version': 1, + 'disable_existing_loggers': False, + 'handlers': { + 'mail_admins': { + 'level': 'ERROR', + 'class': 'django.utils.log.AdminEmailHandler', + }, + 'console': { + 'level':'DEBUG', + 'class':'logging.StreamHandler', + }, + }, + 'loggers': { + 'django.request': { + 'handlers': ['mail_admins'], + 'level': 'ERROR', + 'propagate': True, + }, + } +} + +# schedule settings +SCHEDULE_MIDNIGHT_OFFSET_HOURS=int(env('SCHEDULE_MIDNIGHT_OFFSET_HOURS')) +SCHEDULE_TIMESLOT_LENGTH_MINUTES=int(env('SCHEDULE_TIMESLOT_LENGTH_MINUTES')) + diff --git a/bornhack/settings/development.py b/bornhack/settings/development.py deleted file mode 100644 index 90f451a0..00000000 --- a/bornhack/settings/development.py +++ /dev/null @@ -1,11 +0,0 @@ -from .base import * -import environ -env = environ.Env() -environ.Env.read_env() - -DEBUG = True - -EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' - -# INSTALLED_APPS += ['debug_toolbar', ] - diff --git a/bornhack/settings/production.py b/bornhack/settings/production.py deleted file mode 100644 index 0860e467..00000000 --- a/bornhack/settings/production.py +++ /dev/null @@ -1,38 +0,0 @@ -from .base import * -import environ -env = environ.Env() -environ.Env.read_env() - -DEBUG = False - -EMAIL_HOST = env('EMAIL_HOST') -EMAIL_PORT = env('EMAIL_PORT') -EMAIL_HOST_USER = env('EMAIL_HOST_USER') -EMAIL_HOST_PASSWORD = env('EMAIL_HOST_PASSWORD') -EMAIL_USE_TLS = env('EMAIL_USE_TLS') -DEFAULT_FROM_EMAIL = env('DEFAULT_FROM_EMAIL') -SERVER_EMAIL = env('DEFAULT_FROM_EMAIL') -ARCHIVE_EMAIL = env('ARCHIVE_EMAIL') - -LOGGING = { - 'version': 1, - 'disable_existing_loggers': False, - 'handlers': { - 'mail_admins': { - 'level': 'ERROR', - 'class': 'django.utils.log.AdminEmailHandler', - }, - 'console': { - 'level':'DEBUG', - 'class':'logging.StreamHandler', - }, - }, - 'loggers': { - 'django.request': { - 'handlers': ['mail_admins'], - 'level': 'ERROR', - 'propagate': True, - }, - } -} - diff --git a/bornhack/templates/frontpage.html b/bornhack/templates/frontpage.html deleted file mode 100644 index d4d496c1..00000000 --- a/bornhack/templates/frontpage.html +++ /dev/null @@ -1,132 +0,0 @@ -{% extends 'base.html' %} -{% load static from staticfiles %} - -{% block content %} - -
-
- - -
- -
-
-
- Bornhack is an annual 7 day outdoor tent camping festival - where people with an interest in technology, security and society - come together to celebrate technology, socialise, learn and have fun. -
-
-
- - Tents at CCCamp2015 - -
-
- -
- -
-
- - Jarlsgaard from above - -
-
-
- The first Bornhack took place from August 27 to September 3rd 2016 - on the Danish island of Bornholm. We are preparing - BornHack 2017 which will be from 22nd to 27th of August 2017. -
-
-
- -
- -
-
-

- Bornhack is a participatory event where we expect people - to come up with ideas and projects, and help make the content. We will reach out to - people to ensure interesting, high quality talks and workshops. -

-
-
- - Hacking in Baconsvin village at OHM2013 - -
-
- -
- -

- You are very welcome to ask questions and show your interest on our different channels: -

- {% include 'includes/contact.html' %} -

- Follow and like us to get updates about BornHack. -

- -

- We hope you want to be a part of Bornhack. Ticket sales for 2017 will be open soon! -

- -
-

- Sincerely
- The Bornhack Team -

-
- -

- - Hacking sign at CCCamp2015 - - - Entrance to Baconsvin village at CCCamp2015 - - - Dome being built at CCCamp2015 - - - Person swinging lights in the night at OHM2013 - - - Queer Feminist Geeks village at CCCamp2015 - - - Dome at CCCamp2015 - - - Lights at night at OHM2013 - - - Free Chelsea Manning sign at CCCamp2015 - - - Swedish Embassy sign at CCCamp2015 - -

-
- - -{% endblock %} - diff --git a/bornhack/urls.py b/bornhack/urls.py index be35409a..8e027abe 100644 --- a/bornhack/urls.py +++ b/bornhack/urls.py @@ -10,6 +10,11 @@ from django.conf.urls import include, url from django.contrib import admin from django.views.generic import TemplateView, RedirectView from django.core.urlresolvers import reverse_lazy +from camps.views import * +from info.views import * +from villages.views import * +from program.views import * +from sponsors.views import * urlpatterns = [ url( @@ -24,24 +29,11 @@ urlpatterns = [ r'^news/', include('news.urls', namespace='news') ), - url( - r'^villages/', - include('villages.urls', namespace='villages') - ), - url( - r'^schedule/', - include('program.urls', namespace='schedule') - ), url( r'^$', TemplateView.as_view(template_name='frontpage.html'), name='frontpage' ), - url( - r'^info/', - TemplateView.as_view(template_name='info.html'), - name='info' - ), url( r'^contact/', TemplateView.as_view(template_name='contact.html'), @@ -52,16 +44,6 @@ urlpatterns = [ TemplateView.as_view(template_name='coc.html'), name='conduct' ), - url( - r'^sponsors/', - TemplateView.as_view(template_name='sponsors.html'), - name='call-for-sponsors' - ), - url( - r'^speakers/', - TemplateView.as_view(template_name='speakers.html'), - name='call-for-speakers' - ), url( r'^login/$', LoginView.as_view(), @@ -84,4 +66,105 @@ urlpatterns = [ ), url(r'^accounts/', include('allauth.urls')), url(r'^admin/', include(admin.site.urls)), + + url( + r'^camps/$', + CampListView.as_view(), + name='camp_list' + ), + + # camp specific urls below here + + url( + r'(?P[-_\w+]+)/', include([ + url( + r'^$', + CampDetailView.as_view(), + name='camp_detail' + ), + + url( + r'^info/$', + CampInfoView.as_view(), + name='info' + ), + + url( + r'^schedule/', include([ + url( + r'^(?P\d{4})-(?P\d{2})-(?P\d{2})/$', + ProgramDayView.as_view(), + name='schedule_day' + ), + url( + r'^$', + ProgramOverviewView.as_view(), + name='schedule_index' + ), + url( + r'^speakers/$', + SpeakerListView.as_view(), + name='speaker_index' + ), + url( + r'^speakers/(?P[-_\w+]+)/$', + SpeakerDetailView.as_view(), + name='speaker_detail' + ), + url( + r'^events/$', + EventListView.as_view(), + name='event_index' + ), + url( + r'^call-for-speakers/$', + CallForSpeakersView.as_view(), + name='call_for_speakers' + ), + url( + r'^(?P[-_\w+]+)/$', + EventDetailView.as_view(), + name='event_detail' + ), + ]) + ), + + url( + r'^sponsors/$', + SponsorView.as_view(), + name='sponsors' + ), + + url( + r'^villages/', include([ + url( + r'^$', + VillageListView.as_view(), + name='village_list' + ), + url( + r'create/$', + VillageCreateView.as_view(), + name='village_create' + ), + url( + r'(?P[-_\w+]+)/delete/$', + VillageDeleteView.as_view(), + name='village_delete' + ), + url( + r'(?P[-_\w+]+)/edit/$', + VillageUpdateView.as_view(), + name='village_update' + ), + url( + r'(?P[-_\w+]+)/$', + VillageDetailView.as_view(), + name='village_detail' + ), + ]) + ), + ]) + ) ] + diff --git a/bornhack/wsgi.py b/bornhack/wsgi.py index c8449a8a..8a82caa6 100644 --- a/bornhack/wsgi.py +++ b/bornhack/wsgi.py @@ -11,6 +11,6 @@ import os from django.core.wsgi import get_wsgi_application -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "bornhack.settings.production") +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "bornhack.settings") application = get_wsgi_application() diff --git a/camps/admin.py b/camps/admin.py index 54fda1ab..f777d688 100644 --- a/camps/admin.py +++ b/camps/admin.py @@ -1,29 +1,8 @@ from django.contrib import admin - -from .models import Camp, Day, Expense +from . import models -@admin.register(Expense) -class ExpenseAdmin(admin.ModelAdmin): +@admin.register(models.Camp) +class CampModelAdmin(admin.ModelAdmin): pass - -class ExpenseInlineAdmin(admin.TabularInline): - model = Expense - - -@admin.register(Day) -class DayAdmin(admin.ModelAdmin): - pass - - -class DayInlineAdmin(admin.TabularInline): - model = Day - - -@admin.register(Camp) -class CampAdmin(admin.ModelAdmin): - inlines = [ - DayInlineAdmin, - ExpenseInlineAdmin, - ] diff --git a/camps/context_processors.py b/camps/context_processors.py index 01cdfacf..4f4cadc3 100644 --- a/camps/context_processors.py +++ b/camps/context_processors.py @@ -1,5 +1,23 @@ +from django.conf import settings from .models import Camp +from django.utils import timezone -def current_camp(request): - return {'current_camp': Camp.objects.current()} +def camp(request): + """ + if we have a camp_slug url component then get the "current" Camp object. + Return it after adding the slug to request.session along with a "camps" + queryset containing all camps (used to build the menu and such) + """ + if 'camp_slug' in request.resolver_match.kwargs: + camp = Camp.objects.get(slug=request.resolver_match.kwargs['camp_slug']) + request.session['campslug'] = camp.slug + else: + request.session['campslug'] = None + camp = None + + return { + 'camps': Camp.objects.all().order_by('-camp'), + 'camp': camp + } + diff --git a/camps/managers.py b/camps/managers.py deleted file mode 100644 index 908f1fe4..00000000 --- a/camps/managers.py +++ /dev/null @@ -1,10 +0,0 @@ -from django.utils import timezone -from django.db.models import QuerySet - - -class CampQuerySet(QuerySet): - def current(self): - now = timezone.now() - if self.filter(start__year=now.year).exists(): - return self.get(start__year=now.year) - return None diff --git a/camps/migrations/0007_auto_20161212_1803.py b/camps/migrations/0007_auto_20161212_1803.py new file mode 100644 index 00000000..6aa6c96e --- /dev/null +++ b/camps/migrations/0007_auto_20161212_1803.py @@ -0,0 +1,93 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.4 on 2016-12-12 18:03 +from __future__ import unicode_literals + +import datetime +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +from django.utils.timezone import utc + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('camps', '0006_auto_20160804_1705'), + ] + + operations = [ + migrations.RemoveField( + model_name='day', + name='camp', + ), + migrations.RemoveField( + model_name='camp', + name='shop_open', + ), + migrations.RemoveField( + model_name='expense', + name='amount', + ), + migrations.RemoveField( + model_name='expense', + name='camp', + ), + migrations.RemoveField( + model_name='expense', + name='covered_by', + ), + migrations.RemoveField( + model_name='expense', + name='currency', + ), + migrations.AddField( + model_name='camp', + name='slug', + field=models.SlugField(default='', help_text=b'The url slug to use for this camp', verbose_name=b'Url Slug'), + preserve_default=False, + ), + migrations.AddField( + model_name='expense', + name='dkk_amount', + field=models.DecimalField(decimal_places=2, default=0, help_text=b'The DKK amount of the expense.', max_digits=7, verbose_name=b'DKK Amount'), + preserve_default=False, + ), + migrations.AddField( + model_name='expense', + name='payment_time', + field=models.DateTimeField(default=datetime.datetime(2016, 12, 12, 18, 3, 10, 378604, tzinfo=utc), help_text=b'The date and time this expense was paid.', verbose_name=b'Expense date/time'), + preserve_default=False, + ), + migrations.AddField( + model_name='expense', + name='receipt', + field=models.ImageField(default='', help_text=b'Upload a scan or image of the receipt', upload_to=b'', verbose_name=b'Image of receipt'), + preserve_default=False, + ), + migrations.AddField( + model_name='expense', + name='refund_paid', + field=models.BooleanField(default=False, help_text=b'Has this expense been refunded to the user?', verbose_name=b'Refund paid?'), + ), + migrations.AddField( + model_name='expense', + name='refund_user', + field=models.ForeignKey(blank=True, help_text=b'Which user, if any, covered this expense and should be refunded.', null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name=b'Refund user'), + ), + migrations.AlterField( + model_name='camp', + name='end', + field=models.DateTimeField(help_text=b'When the camp ends.', verbose_name=b'End date'), + ), + migrations.AlterField( + model_name='camp', + name='name', + field=models.CharField(help_text=b'Name of the camp, ie. Bornhack 2016.', max_length=255, verbose_name=b'Name'), + ), + migrations.AlterField( + model_name='camp', + name='start', + field=models.DateTimeField(help_text=b'When the camp starts.', verbose_name=b'Start date'), + ), + ] diff --git a/camps/migrations/0008_delete_day.py b/camps/migrations/0008_delete_day.py new file mode 100644 index 00000000..58ad3137 --- /dev/null +++ b/camps/migrations/0008_delete_day.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.4 on 2016-12-12 18:09 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('program', '0010_auto_20161212_1809'), + ('camps', '0007_auto_20161212_1803'), + ] + + operations = [ + migrations.DeleteModel( + name='Day', + ), + ] diff --git a/camps/migrations/0009_auto_20161220_1645.py b/camps/migrations/0009_auto_20161220_1645.py new file mode 100644 index 00000000..c6214432 --- /dev/null +++ b/camps/migrations/0009_auto_20161220_1645.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.4 on 2016-12-20 16:45 +from __future__ import unicode_literals + +import datetime +from django.db import migrations, models +from django.utils.timezone import utc + + +class Migration(migrations.Migration): + + dependencies = [ + ('camps', '0008_delete_day'), + ] + + operations = [ + migrations.RenameField( + model_name='camp', + old_name='end', + new_name='camp_end', + ), + migrations.RenameField( + model_name='camp', + old_name='start', + new_name='camp_start', + ), + migrations.AddField( + model_name='camp', + name='buildup_start', + field=models.DateTimeField(default=datetime.datetime(2016, 12, 20, 16, 45, 39, 609630, tzinfo=utc), help_text=b'When the camp buildup starts.', verbose_name=b'Buildup Start date'), + preserve_default=False, + ), + migrations.AddField( + model_name='camp', + name='teardown_end', + field=models.DateTimeField(default=datetime.datetime(2016, 12, 20, 16, 45, 44, 532143, tzinfo=utc), help_text=b'When the camp teardown ends.', verbose_name=b'Start date'), + preserve_default=False, + ), + ] diff --git a/camps/migrations/0010_auto_20161220_1714.py b/camps/migrations/0010_auto_20161220_1714.py new file mode 100644 index 00000000..770763dd --- /dev/null +++ b/camps/migrations/0010_auto_20161220_1714.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.4 on 2016-12-20 17:14 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('camps', '0009_auto_20161220_1645'), + ] + + operations = [ + migrations.RemoveField( + model_name='camp', + name='name', + ), + migrations.AddField( + model_name='camp', + name='tagline', + field=models.CharField(default='', help_text=b'Tagline of the camp, ie. "Initial Commit"', max_length=255, verbose_name=b'Tagline'), + preserve_default=False, + ), + migrations.AddField( + model_name='camp', + name='title', + field=models.CharField(default='', help_text=b'Title of the camp, ie. Bornhack 2016.', max_length=255, verbose_name=b'Title'), + preserve_default=False, + ), + ] diff --git a/camps/migrations/0011_auto_20161228_1750.py b/camps/migrations/0011_auto_20161228_1750.py new file mode 100644 index 00000000..0f304a50 --- /dev/null +++ b/camps/migrations/0011_auto_20161228_1750.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.4 on 2016-12-28 17:50 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('camps', '0010_auto_20161220_1714'), + ] + + operations = [ + migrations.AddField( + model_name='camp', + name='logo_large', + field=models.CharField(default='', help_text=b'The filename of the large logo to use on the frontpage of this camp', max_length=100, verbose_name=b'Large logo for this camp'), + preserve_default=False, + ), + migrations.AddField( + model_name='camp', + name='logo_small', + field=models.CharField(default='', help_text=b'The filename of the small logo to use in the top of the page for this camp', max_length=100, verbose_name=b'Small logo for this camp'), + preserve_default=False, + ), + ] diff --git a/camps/migrations/0012_auto_20161228_2312.py b/camps/migrations/0012_auto_20161228_2312.py new file mode 100644 index 00000000..c9fc86fc --- /dev/null +++ b/camps/migrations/0012_auto_20161228_2312.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.4 on 2016-12-28 23:12 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('camps', '0011_auto_20161228_1750'), + ] + + operations = [ + migrations.RemoveField( + model_name='camp', + name='logo_large', + ), + migrations.RemoveField( + model_name='camp', + name='logo_small', + ), + ] diff --git a/camps/migrations/0013_auto_20161229_2201.py b/camps/migrations/0013_auto_20161229_2201.py new file mode 100644 index 00000000..33298454 --- /dev/null +++ b/camps/migrations/0013_auto_20161229_2201.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.4 on 2016-12-29 22:01 +from __future__ import unicode_literals + +import django.contrib.postgres.fields.ranges +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('camps', '0012_auto_20161228_2312'), + ] + + operations = [ + migrations.RemoveField( + model_name='camp', + name='buildup_start', + ), + migrations.RemoveField( + model_name='camp', + name='camp_end', + ), + migrations.RemoveField( + model_name='camp', + name='camp_start', + ), + migrations.RemoveField( + model_name='camp', + name='teardown_end', + ), + migrations.AddField( + model_name='camp', + name='buildup', + field=django.contrib.postgres.fields.ranges.DateTimeRangeField(help_text=b'The camp buildup period.', null=True, verbose_name=b'Buildup Period'), + ), + migrations.AddField( + model_name='camp', + name='camp', + field=django.contrib.postgres.fields.ranges.DateTimeRangeField(help_text=b'The camp period.', null=True, verbose_name=b'Camp Period'), + ), + migrations.AddField( + model_name='camp', + name='teardown', + field=django.contrib.postgres.fields.ranges.DateTimeRangeField(help_text=b'The camp teardown period.', null=True, verbose_name=b'Teardown period'), + ), + ] diff --git a/camps/migrations/0014_auto_20161229_2202.py b/camps/migrations/0014_auto_20161229_2202.py new file mode 100644 index 00000000..765871e8 --- /dev/null +++ b/camps/migrations/0014_auto_20161229_2202.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.4 on 2016-12-29 22:02 +from __future__ import unicode_literals + +import django.contrib.postgres.fields.ranges +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('camps', '0013_auto_20161229_2201'), + ] + + operations = [ + migrations.AlterField( + model_name='camp', + name='buildup', + field=django.contrib.postgres.fields.ranges.DateTimeRangeField(help_text=b'The camp buildup period.', verbose_name=b'Buildup Period'), + ), + migrations.AlterField( + model_name='camp', + name='camp', + field=django.contrib.postgres.fields.ranges.DateTimeRangeField(help_text=b'The camp period.', verbose_name=b'Camp Period'), + ), + migrations.AlterField( + model_name='camp', + name='teardown', + field=django.contrib.postgres.fields.ranges.DateTimeRangeField(help_text=b'The camp teardown period.', verbose_name=b'Teardown period'), + ), + ] diff --git a/camps/migrations/0015_auto_20170116_1634.py b/camps/migrations/0015_auto_20170116_1634.py new file mode 100644 index 00000000..fa87cc72 --- /dev/null +++ b/camps/migrations/0015_auto_20170116_1634.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.4 on 2017-01-16 16:34 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('camps', '0014_auto_20161229_2202'), + ] + + operations = [ + migrations.RemoveField( + model_name='expense', + name='refund_user', + ), + migrations.DeleteModel( + name='Expense', + ), + ] diff --git a/camps/migrations/0016_camp_description.py b/camps/migrations/0016_camp_description.py new file mode 100644 index 00000000..900f6cdc --- /dev/null +++ b/camps/migrations/0016_camp_description.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.4 on 2017-01-16 16:37 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('camps', '0015_auto_20170116_1634'), + ] + + operations = [ + migrations.AddField( + model_name='camp', + name='description', + field=models.TextField(default=b'', help_text=b'Description of the camp, shown on the camp frontpage. HTML and markdown supported.', verbose_name=b'Description'), + ), + ] diff --git a/camps/migrations/0017_remove_camp_description.py b/camps/migrations/0017_remove_camp_description.py new file mode 100644 index 00000000..a5ade35b --- /dev/null +++ b/camps/migrations/0017_remove_camp_description.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.4 on 2017-01-16 21:59 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('camps', '0016_camp_description'), + ] + + operations = [ + migrations.RemoveField( + model_name='camp', + name='description', + ), + ] diff --git a/camps/mixins.py b/camps/mixins.py new file mode 100644 index 00000000..fca375da --- /dev/null +++ b/camps/mixins.py @@ -0,0 +1,14 @@ +from camps.models import Camp +from django.shortcuts import get_object_or_404 + + +class CampViewMixin(object): + def dispatch(self, request, *args, **kwargs): + self.camp = get_object_or_404(Camp, slug=self.kwargs['camp_slug']) + return super(CampViewMixin, self).dispatch(request, *args, **kwargs) + + def get_queryset(self): + queryset = super(CampViewMixin, self).get_queryset() + return queryset.filter(camp=self.camp) + + diff --git a/camps/models.py b/camps/models.py index bcbeb731..52a6a41a 100644 --- a/camps/models.py +++ b/camps/models.py @@ -1,135 +1,158 @@ import datetime from django.db import models -from django.utils.translation import ugettext_lazy as _ - from utils.models import UUIDModel, CreatedUpdatedModel - -from .managers import CampQuerySet +from program.models import EventType +from django.contrib.postgres.fields import DateTimeRangeField +from psycopg2.extras import DateTimeTZRange +from django.core.exceptions import ValidationError +from datetime import timedelta class Camp(CreatedUpdatedModel, UUIDModel): class Meta: - verbose_name = _('Camp') - verbose_name_plural = _('Camps') + verbose_name = 'Camp' + verbose_name_plural = 'Camps' - name = models.CharField( - verbose_name=_('Name'), - help_text=_('Name of the camp, ie. Bornhack.'), + title = models.CharField( + verbose_name='Title', + help_text='Title of the camp, ie. Bornhack 2016.', max_length=255, ) - start = models.DateTimeField( - verbose_name=_('Start date'), - help_text=_('When the camp starts.'), - unique=True, - ) - - end = models.DateTimeField( - verbose_name=_('End date'), - help_text=_('When the camp ends.'), - unique=True, - ) - - shop_open = models.BooleanField( - verbose_name=_('Shop open?'), - help_text=_('Whether the shop is open or not.'), - default=False, - ) - - objects = CampQuerySet.as_manager() - - def __str__(self): - return _('{} {}').format( - self.name, - self.start.year, - ) - - def create_days(self): - delta = self.end - self.start - for day_offset in range(0, delta.days + 1): - day, created = self.days.get_or_create( - date=self.start + datetime.timedelta(days=day_offset) - ) - - def save(self, **kwargs): - super(Camp, self).save(**kwargs) - self.create_days() - - -class Day(CreatedUpdatedModel, UUIDModel): - class Meta: - verbose_name = _('Day') - verbose_name_plural = _('Days') - ordering = ['date'] - - camp = models.ForeignKey( - 'camps.Camp', - verbose_name=_('Camp'), - help_text=_('Which camp does this day belong to.'), - related_name='days', - ) - - date = models.DateField( - verbose_name=_('Date'), - help_text=_('What date?') - ) - - def __str__(self): - return '{} ({})'.format( - self.date.strftime('%A'), - self.date - ) - - -class Expense(CreatedUpdatedModel, UUIDModel): - class Meta: - verbose_name = _('Expense') - verbose_name_plural = _('Expenses') - - camp = models.ForeignKey( - 'camps.Camp', - verbose_name=_('Camp'), - help_text=_('The camp to which this expense relates to.'), - ) - - description = models.CharField( - verbose_name=_('Description'), - help_text=_('What this expense covers.'), + tagline = models.CharField( + verbose_name='Tagline', + help_text='Tagline of the camp, ie. "Initial Commit"', max_length=255, ) - amount = models.DecimalField( - verbose_name=_('Amount'), - help_text=_('The amount of the expense.'), - max_digits=7, - decimal_places=2, + slug = models.SlugField( + verbose_name='Url Slug', + help_text='The url slug to use for this camp' ) - CURRENCIES = [ - ('btc', 'BTC'), - ('dkk', 'DKK'), - ('eur', 'EUR'), - ('sek', 'SEK'), - ] - - currency = models.CharField( - verbose_name=_('Currency'), - help_text=_('What currency the amount is in.'), - choices=CURRENCIES, - max_length=3, + buildup = DateTimeRangeField( + verbose_name='Buildup Period', + help_text='The camp buildup period.', ) - covered_by = models.ForeignKey( - 'auth.User', - verbose_name=_('Covered by'), - help_text=_('Which user, if any, covered this expense.'), - null=True, - blank=True, + camp = DateTimeRangeField( + verbose_name='Camp Period', + help_text='The camp period.', ) - def __str__(self): - return _('{} {} for {}').format( - self.amount, - self.get_currency_display(), - self.camp, - ) + teardown = DateTimeRangeField( + verbose_name='Teardown period', + help_text='The camp teardown period.', + ) + + def clean(self): + ''' Make sure the dates make sense - meaning no overlaps and buildup before camp before teardown ''' + errors = [] + # sanity checking for buildup + if self.buildup.lower > self.buildup.upper: + errors.append(ValidationError({'buildup', 'Start of buildup must be before end of buildup'})) + + # sanity checking for camp + if self.camp.lower > self.camp.upper: + errors.append(ValidationError({'camp', 'Start of camp must be before end of camp'})) + + # sanity checking for teardown + if self.teardown.lower > self.teardown.upper: + errors.append(ValidationError({'teardown', 'Start of teardown must be before end of teardown'})) + + # check for overlaps buildup vs. camp + if self.buildup.upper > self.camp.lower: + msg = "End of buildup must not be after camp start" + errors.append(ValidationError({'buildup', msg})) + errors.append(ValidationError({'camp', msg})) + + # check for overlaps camp vs. teardown + if self.camp.upper > self.teardown.lower: + msg = "End of camp must not be after teardown start" + errors.append(ValidationError({'camp', msg})) + errors.append(ValidationError({'teardown', msg})) + + if errors: + raise ValidationError(errors) + + def __unicode__(self): + return "%s - %s" % (self.title, self.tagline) + + @property + def event_types(self): + # return all event types with at least one event in this camp + return EventType.objects.filter(event__instances__isnull=False, event__camp=self).distinct() + + @property + def logo_small(self): + return 'img/%(slug)s/logo/%(slug)s-logo-small.png' % {'slug': self.slug} + + @property + def logo_large(self): + return 'img/%(slug)s/logo/%(slug)s-logo-large.png' % {'slug': self.slug} + + def get_days(self, camppart): + ''' + Returns a list of DateTimeTZRanges representing the days during the specified part of the camp. + ''' + if not hasattr(self, camppart): + print("nonexistant field/attribute") + return False + + field = getattr(self, camppart) + + if not hasattr(field, '__class__') or not hasattr(field.__class__, '__name__') or not field.__class__.__name__ == 'DateTimeTZRange': + print("this attribute is not a datetimetzrange field: %s" % field) + return False + + daycount = (field.upper - field.lower).days + days = [] + for i in range(0, daycount): + if i == 0: + # on the first day use actual start time instead of midnight + days.append( + DateTimeTZRange( + field.lower, + (field.lower+timedelta(days=i+1)).replace(hour=0) + ) + ) + elif i == daycount-1: + # on the last day use actual end time instead of midnight + days.append( + DateTimeTZRange( + (field.lower+timedelta(days=i)).replace(hour=0), + field.lower+timedelta(days=i+1) + ) + ) + else: + # neither first nor last day, goes from midnight to midnight + days.append( + DateTimeTZRange( + (field.lower+timedelta(days=i)).replace(hour=0), + (field.lower+timedelta(days=i+1)).replace(hour=0) + ) + ) + return days + + @property + def buildup_days(self): + ''' + Returns a list of DateTimeTZRanges representing the days during the buildup. + ''' + return self.get_days('buildup') + + @property + def camp_days(self): + ''' + Returns a list of DateTimeTZRanges representing the days during the camp. + ''' + return self.get_days('camp') + + @property + def teardown_days(self): + ''' + Returns a list of DateTimeTZRanges representing the days during the buildup. + ''' + return self.get_days('teardown') + + diff --git a/camps/templates/camp_detail_bornhack-2016.html b/camps/templates/camp_detail_bornhack-2016.html new file mode 100644 index 00000000..f5dd3016 --- /dev/null +++ b/camps/templates/camp_detail_bornhack-2016.html @@ -0,0 +1,61 @@ +{% extends 'base.html' %} +{% load commonmark %} +{% load static from staticfiles %} +{% load imageutils %} +{% block content %} +
+
+ + +
+ +
+
+ Bornhack 2016 was the first BornHack. It took place from August 27 to September 3rd 2016 on the Danish island of Bornholm. The tagline of this event was Initial Commit. +
+
+
+ {% thumbnail 'img/bornhack-2016/esbjerg' '1600x988-B12A2612.jpg' 'A quiet moment in the chillout area by the bar' %} +
+
+
+ +
+ +
+
+ {% thumbnail 'img/bornhack-2016/fonsmark' 'FB1_5128.JPG' 'Emma Holten speaking at BornHack 2016' %} +
+
+
+ With 3 keynotes, 38 talks, and 9 workshops we had an ambitious program which was well received by participants. We also had an amazing list of sponsors making the event financially possible. +
+
+
+ +
+ +
+
+

Many of the speakers and keynotes were present for the full week, providing participants with ample opportunity to discuss projects and exchange ideas.

+
+
+ {% thumbnail 'img/bornhack-2016/fonsmark' 'FB1_5149.JPG' 'Danish politicians debating at BornHack 2016' %} +
+
+ +
+ +
+
+ {% thumbnail 'img/bornhack-2016/esbjerg' '1600x988-B12A2631.jpg' 'The BornHack 2016 organiser team' %} +
+
+
+ The team behind BornHack 2016 had a lot of help from volunteer participants when it was needed. We are very grateful and will repay the kindness by continuing to organise awesome events. +
+
+
+ +{% endblock content %} + diff --git a/camps/templates/camp_detail_bornhack-2017.html b/camps/templates/camp_detail_bornhack-2017.html new file mode 100644 index 00000000..c11ecd88 --- /dev/null +++ b/camps/templates/camp_detail_bornhack-2017.html @@ -0,0 +1,61 @@ +{% extends 'base.html' %} +{% load commonmark %} +{% load static from staticfiles %} +{% load imageutils %} +{% block content %} +
+
+ + +
+ +
+
+ Bornhack 2017 will be the second BornHack. It will take place from August 22nd to August 29th 2017 on the Danish island of Bornholm. The tagline of this event will be Make Tradition. +
+
+
+ {% thumbnail 'img/bornhack-2016/esbjerg' '1600x988-B12A2610.jpg' 'The family area at BornHack 2016' %} +
+
+
+ +
+ +
+
+ {% thumbnail 'img/bornhack-2016/esbjerg' '1600x988-B12A2631.jpg' 'The BornHack 2016 organiser team' %} +
+
+
+ The BornHack team looks forward to organising another great event for the hacker community. We still need volunteers, so please let us know if you want to help! +
+
+
+ +
+ +
+
+
We want to encourage hackers, makers, politicians, activists, developers, artists, sysadmins, engineers and anyone else with an interest in technology and society to read our call for speakers.
+
+
+ {% thumbnail 'img/bornhack-2016/fonsmark' 'FB1_5149.JPG' 'Danish politicians debating at BornHack 2016' %} +
+
+ +
+ +
+
+ {% thumbnail 'img/bornhack-2016/fonsmark' 'FB1_5265.JPG' 'Organisers thanking the BornHack 2016 sponsors' %} +
+
+
+ BornHack aims to keep ticket prices affordable for everyone and to that end we need sponsors. Please see our call for sponsors if you want to sponsor us, or if you work for a company you think might be able to help. +
+
+
+ +{% endblock content %} + diff --git a/camps/templates/camp_list.html b/camps/templates/camp_list.html new file mode 100644 index 00000000..5119693c --- /dev/null +++ b/camps/templates/camp_list.html @@ -0,0 +1,43 @@ +{% extends 'base.html' %} +{% load commonmark %} +{% load static from staticfiles %} +{% load imageutils %} +{% block content %} +
+
+ + + + + + + + + + + + {% for listcamp in camps %} + + + + + + + + + {% empty %} +

No camps found, what gives?!

+ {% endfor %} + +
NameTaglineBuildupCampTeardown
{{ listcamp.title }}{{ listcamp.tagline }}{{ listcamp.buildup.lower }} to {{ listcamp.buildup.upper }}{{ listcamp.camp.lower }} to {{ listcamp.camp.upper }}{{ listcamp.teardown.lower }} to {{ listcamp.teardown.upper }}
+
+
+ +{% endblock content %} + diff --git a/camps/templates/camps/camp_form.html b/camps/templates/camps/camp_form.html deleted file mode 100644 index bbc491d7..00000000 --- a/camps/templates/camps/camp_form.html +++ /dev/null @@ -1,4 +0,0 @@ -
- {{ form }} - -
diff --git a/camps/templates/camps/camp_list.html b/camps/templates/camps/camp_list.html deleted file mode 100644 index 1edeb228..00000000 --- a/camps/templates/camps/camp_list.html +++ /dev/null @@ -1,3 +0,0 @@ -{% for camp in camps %} - {{ camp }}
-{% endfor %} diff --git a/camps/views.py b/camps/views.py index 7c378d64..d83c7527 100644 --- a/camps/views.py +++ b/camps/views.py @@ -1,3 +1,17 @@ -from . import models +from django.views.generic import ListView, DetailView +from django.utils import timezone +from .models import * +class CampDetailView(DetailView): + model = Camp + slug_url_kwarg = 'camp_slug' + + def get_template_names(self): + return 'camp_detail_%s.html' % self.get_object().slug + + +class CampListView(ListView): + model = Camp + template_name = 'camp_list.html' + diff --git a/graphics/playground.svg b/graphics/playground.svg deleted file mode 100644 index 912e1871..00000000 --- a/graphics/playground.svg +++ /dev/null @@ -1,1905 +0,0 @@ - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - BORNHACK - initial commit - BORNHACK - - BORNHACK - - - - - - - - - - - - - - - - - - - - initial commit - BORNHACK - - - initial commit - - - initial commit - BORNHACK - - AUGUST 2016BORNHOLM - - - - BORNHACK - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - BORNHACK.DK - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Initial commitbornhack.com27/8-3/9 2016 - - - - - - - - - - - - - - - - - - - - - - - - - - - - Initial commit - bornhack.dk25/8-4/9 2016 - - - - - villans bewarebrainy hackers andbrave superheroes only! - Initial commit27/8-3/9 2016 - - .DK - - - - - - - - - - - - Initial commit 27/8-3/9 2016 - - diff --git a/bornhack/settings/__init__.py b/info/__init__.py similarity index 100% rename from bornhack/settings/__init__.py rename to info/__init__.py diff --git a/info/admin.py b/info/admin.py new file mode 100644 index 00000000..e8b9235a --- /dev/null +++ b/info/admin.py @@ -0,0 +1,6 @@ +from django.contrib import admin +from .models import * + +admin.site.register(InfoCategory) +admin.site.register(InfoItem) + diff --git a/info/apps.py b/info/apps.py new file mode 100644 index 00000000..f4738cf0 --- /dev/null +++ b/info/apps.py @@ -0,0 +1,7 @@ +from __future__ import unicode_literals + +from django.apps import AppConfig + + +class InfoConfig(AppConfig): + name = 'info' diff --git a/info/migrations/0001_initial.py b/info/migrations/0001_initial.py new file mode 100644 index 00000000..f554d348 --- /dev/null +++ b/info/migrations/0001_initial.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.4 on 2016-12-24 22:11 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('camps', '0010_auto_20161220_1714'), + ] + + operations = [ + migrations.CreateModel( + name='InfoCategory', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', models.DateTimeField(auto_now_add=True)), + ('updated', models.DateTimeField(auto_now=True)), + ('headline', models.CharField(help_text='The headline of this info category', max_length=100)), + ('anchor', models.SlugField(help_text='The HTML anchor to use for this info category.')), + ('weight', models.PositiveIntegerField(help_text='Determines sorting/ordering. Heavier categories sink to the bottom. Categories with the same weight are ordered alphabetically.')), + ('camp', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='infocategories', to='camps.Camp')), + ], + options={ + 'ordering': ['-weight', 'headline'], + }, + ), + migrations.CreateModel( + name='InfoItem', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', models.DateTimeField(auto_now_add=True)), + ('updated', models.DateTimeField(auto_now=True)), + ('headline', models.CharField(help_text='Headline of this info item.', max_length=100)), + ('anchor', models.SlugField(help_text='The HTML anchor to use for this info item.')), + ('body', models.TextField(help_text='Body of this info item. Markdown is supported.')), + ('weight', models.PositiveIntegerField(help_text='Determines sorting/ordering. Heavier items sink to the bottom. Items with the same weight are ordered alphabetically.')), + ('category', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='infoitems', to='info.InfoCategory')), + ], + options={ + 'ordering': ['-weight', 'headline'], + }, + ), + migrations.AlterUniqueTogether( + name='infoitem', + unique_together=set([('headline', 'category'), ('anchor', 'category')]), + ), + migrations.AlterUniqueTogether( + name='infocategory', + unique_together=set([('headline', 'camp'), ('anchor', 'camp')]), + ), + ] diff --git a/info/migrations/0002_auto_20161228_2312.py b/info/migrations/0002_auto_20161228_2312.py new file mode 100644 index 00000000..33317daf --- /dev/null +++ b/info/migrations/0002_auto_20161228_2312.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.4 on 2016-12-28 23:12 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('info', '0001_initial'), + ] + + operations = [ + migrations.AlterModelOptions( + name='infocategory', + options={'ordering': ['-weight', 'headline'], 'verbose_name_plural': 'Info Categories'}, + ), + migrations.AlterField( + model_name='infocategory', + name='weight', + field=models.PositiveIntegerField(default=100, help_text='Determines sorting/ordering. Heavier categories sink to the bottom. Categories with the same weight are ordered alphabetically. Defaults to 100.'), + ), + migrations.AlterField( + model_name='infoitem', + name='weight', + field=models.PositiveIntegerField(default=100, help_text='Determines sorting/ordering. Heavier items sink to the bottom. Items with the same weight are ordered alphabetically. Defaults to 100.'), + ), + ] diff --git a/info/migrations/__init__.py b/info/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/info/models.py b/info/models.py new file mode 100644 index 00000000..d7962448 --- /dev/null +++ b/info/models.py @@ -0,0 +1,80 @@ +from __future__ import unicode_literals +from django.contrib import messages +from django.db import models +from utils.models import CreatedUpdatedModel +from django.core.exceptions import ValidationError + + +class InfoCategory(CreatedUpdatedModel): + class Meta: + ordering = ['-weight', 'headline'] + unique_together = (('anchor', 'camp'), ('headline', 'camp')) + verbose_name_plural = "Info Categories" + + camp = models.ForeignKey( + 'camps.Camp', + related_name = 'infocategories', + on_delete = models.PROTECT + ) + + headline = models.CharField( + max_length = 100, + help_text = "The headline of this info category" + ) + + anchor = models.SlugField( + help_text = "The HTML anchor to use for this info category." + ) + + weight = models.PositiveIntegerField( + help_text = 'Determines sorting/ordering. Heavier categories sink to the bottom. Categories with the same weight are ordered alphabetically. Defaults to 100.', + default = 100, + ) + + def clean(self): + if InfoItem.objects.filter(category__camp=self.camp, anchor=self.anchor).exists(): + # this anchor is already in use on an item, so it cannot be used (must be unique on the page) + raise ValidationError({'anchor': 'Anchor is already in use on an info item for this camp'}) + + def __str__(self): + return '%s (%s)' % (self.headline, self.camp) + + +class InfoItem(CreatedUpdatedModel): + class Meta: + ordering = ['-weight', 'headline'] + unique_together = (('anchor', 'category'), ('headline', 'category')) + + category = models.ForeignKey( + 'info.InfoCategory', + related_name = 'infoitems', + on_delete = models.PROTECT + ) + + headline = models.CharField( + max_length = 100, + help_text = "Headline of this info item." + ) + + anchor = models.SlugField( + help_text = "The HTML anchor to use for this info item." + ) + + body = models.TextField( + help_text = 'Body of this info item. Markdown is supported.' + ) + + weight = models.PositiveIntegerField( + help_text = 'Determines sorting/ordering. Heavier items sink to the bottom. Items with the same weight are ordered alphabetically. Defaults to 100.', + default = 100, + ) + + def clean(self): + if InfoCategory.objects.filter(camp=self.category.camp, anchor=self.anchor).exists(): + # this anchor is already in use on a category, so it cannot be used here (they must be unique on the entire page) + raise ValidationError({'anchor': 'Anchor is already in use on an info category for this camp'}) + + def __str__(self): + return '%s (%s)' % (self.headline, self.category) + + diff --git a/info/templates/info.html b/info/templates/info.html new file mode 100644 index 00000000..2f2cb05a --- /dev/null +++ b/info/templates/info.html @@ -0,0 +1,70 @@ +{% extends 'base.html' %} +{% load static from staticfiles %} + +{% block title %} +Info | {{ block.super }} +{% endblock %} + +{% block extra_head %} + +{% endblock %} + +{% block content %} + + + {% if categories %} +
+
+

Table of Contents

+

+ {% for category in categories %} + {{ category.headline }} + {% endfor %} +

+
+
+ + {% for category in categories %} + +
+
+

{{ category.title }}

+
+ {% for item in category.infoitems.all %} +
+
+ +

{{ item.headline }} + + + +

+
+
+

{{ item.body }}

+
+
+ {% endfor %} +
+
+
+ {% endfor %} + {% else %} +

No info found for {{ camp.title }}

+ {% endif %} +{% endblock %} + diff --git a/camps/tests.py b/info/tests.py similarity index 100% rename from camps/tests.py rename to info/tests.py diff --git a/info/views.py b/info/views.py new file mode 100644 index 00000000..08e14ea5 --- /dev/null +++ b/info/views.py @@ -0,0 +1,16 @@ +from django.shortcuts import render +from django.views.generic import ListView, DetailView +from django.utils import timezone +from .models import * +from camps.mixins import CampViewMixin + +class CampInfoView(CampViewMixin, ListView): + model = InfoCategory + template_name = 'info.html' + context_object_name = 'categories' + + def get_queryset(self): + queryset = super(CampInfoView, self).get_queryset() + # do not show categories with 0 items + return queryset.exclude(infoitems__isnull=True) + diff --git a/manage.py b/manage.py index 3fd180c5..fc2fdcda 100755 --- a/manage.py +++ b/manage.py @@ -5,7 +5,7 @@ import sys if __name__ == "__main__": os.environ.setdefault( "DJANGO_SETTINGS_MODULE", - "bornhack.settings.production" + "bornhack.settings" ) from django.core.management import execute_from_command_line diff --git a/news/admin.py b/news/admin.py index 587c9ff9..ea5a22a9 100644 --- a/news/admin.py +++ b/news/admin.py @@ -5,5 +5,15 @@ from . import models @admin.register(models.NewsItem) class NewsItemModelAdmin(admin.ModelAdmin): - list_display = ['title', 'public', 'published_at'] - list_filter = ['public'] + list_display = ['title', 'published_at', 'archived'] + actions = ['archive_news_items', 'unarchive_news_items'] + + def archive_news_items(self, request, queryset): + queryset.filter(archived=False).update(archived=True) + archive_news_items.description = 'Mark newsitem(s) as archived' + + def unarchive_news_items(self, request, queryset): + queryset.filter(archived=True).update(archived=False) + unarchive_news_items.description = 'Mark newsitem(s) as not archived' + + diff --git a/news/managers.py b/news/managers.py deleted file mode 100644 index c25424f5..00000000 --- a/news/managers.py +++ /dev/null @@ -1,11 +0,0 @@ -from django.db.models import QuerySet -from django.utils import timezone - - -class NewsItemQuerySet(QuerySet): - - def public(self): - return self.filter( - public=True, - published_at__lt=timezone.now() - ) diff --git a/news/migrations/0006_remove_newsitem_public.py b/news/migrations/0006_remove_newsitem_public.py new file mode 100644 index 00000000..6577b1bc --- /dev/null +++ b/news/migrations/0006_remove_newsitem_public.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.4 on 2016-12-20 11:27 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('news', '0005_auto_20160618_1902'), + ] + + operations = [ + migrations.RemoveField( + model_name='newsitem', + name='public', + ), + ] diff --git a/news/migrations/0007_auto_20161220_1136.py b/news/migrations/0007_auto_20161220_1136.py new file mode 100644 index 00000000..7d03a585 --- /dev/null +++ b/news/migrations/0007_auto_20161220_1136.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.4 on 2016-12-20 11:36 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('news', '0006_remove_newsitem_public'), + ] + + operations = [ + migrations.AlterField( + model_name='newsitem', + name='published_at', + field=models.DateTimeField(blank=True, null=True), + ), + ] diff --git a/news/migrations/0008_newsitem_archived.py b/news/migrations/0008_newsitem_archived.py new file mode 100644 index 00000000..ec2cf548 --- /dev/null +++ b/news/migrations/0008_newsitem_archived.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.4 on 2016-12-20 13:03 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('news', '0007_auto_20161220_1136'), + ] + + operations = [ + migrations.AddField( + model_name='newsitem', + name='archived', + field=models.BooleanField(default=False), + ), + ] diff --git a/news/models.py b/news/models.py index 4b08b223..7e6995c7 100644 --- a/news/models.py +++ b/news/models.py @@ -5,7 +5,6 @@ from django.utils import encoding from django.utils.text import slugify from utils.models import CreatedUpdatedModel -from news.managers import NewsItemQuerySet @encoding.python_2_unicode_compatible @@ -15,36 +14,33 @@ class NewsItem(CreatedUpdatedModel): title = models.CharField(max_length=100) content = models.TextField() - public = models.BooleanField(default=False) - published_at = models.DateTimeField() + published_at = models.DateTimeField(null=True, blank=True) slug = models.SlugField(max_length=255, blank=True) + archived = models.BooleanField(default=False) def __str__(self): return self.title def save(self, **kwargs): - if ( - not self.pk or - not self.slug or - NewsItem.objects.filter(slug=self.slug).count() > 1 - ): - published_at_string = self.published_at.strftime('%Y-%m-%d') - base_slug = slugify(self.title) - slug = '{}-{}'.format(published_at_string, base_slug) - incrementer = 1 + if self.published_at: + # if this is a new newsitem, or it doesn't have a slug, or the slug is in use on another item, create a new slug + if (not self.pk or not self.slug or NewsItem.objects.filter(slug=self.slug).count() > 1): + published_at_string = self.published_at.strftime('%Y-%m-%d') + base_slug = slugify(self.title) + slug = '{}-{}'.format(published_at_string, base_slug) + incrementer = 1 - # We have to make sure that the slug won't clash with current slugs - while NewsItem.objects.filter(slug=slug).exists(): - if incrementer == 1: - slug = '{}-1'.format(slug) - else: - slug = '{}-{}'.format( - '-'.join(slug.split('-')[:-1]), - incrementer - ) - incrementer += 1 - self.slug = slug + # We have to make sure that the slug won't clash with current slugs + while NewsItem.objects.filter(slug=slug).exists(): + if incrementer == 1: + slug = '{}-1'.format(slug) + else: + slug = '{}-{}'.format( + '-'.join(slug.split('-')[:-1]), + incrementer + ) + incrementer += 1 + self.slug = slug super(NewsItem, self).save(**kwargs) - objects = NewsItemQuerySet.as_manager() diff --git a/news/templates/news_index.html b/news/templates/news_index.html index 30ee0995..6b2760bb 100644 --- a/news/templates/news_index.html +++ b/news/templates/news_index.html @@ -6,6 +6,9 @@ News | {{ block.super }} {% endblock %} {% block content %} +{% if request.resolver_match.kwargs.archived %} +Showing archived news items. Show regular news items +{% endif %} {% for item in news_items %}

{{ item.title }} {{ item.published_at|date:"Y-m-d" }}

@@ -17,4 +20,9 @@ News | {{ block.super }} {% empty %}

No news yet. Stay tuned!

{% endfor %} -{% endblock %} \ No newline at end of file +{% if not request.resolver_match.kwargs.archived %} +
+Show archived news items +{% endif %} +{% endblock %} + diff --git a/news/urls.py b/news/urls.py index 1d1a0b65..766b4e3c 100644 --- a/news/urls.py +++ b/news/urls.py @@ -3,6 +3,8 @@ from . import views urlpatterns = [ - url(r'^$', views.NewsIndex.as_view(), name='index'), + url(r'^$', views.NewsIndex.as_view(), kwargs={'archived': False}, name='index'), + url(r'^archive/$', views.NewsIndex.as_view(), kwargs={'archived': True}, name='archive'), url(r'(?P[-_\w+]+)/$', views.NewsDetail.as_view(), name='detail'), ] + diff --git a/news/views.py b/news/views.py index 45891c12..8056bc9d 100644 --- a/news/views.py +++ b/news/views.py @@ -1,33 +1,22 @@ from django.views.generic import ListView, DetailView from django.utils import timezone -from . import models - +from .models import * class NewsIndex(ListView): - model = models.NewsItem + model = NewsItem template_name = 'news_index.html' context_object_name = 'news_items' def get_queryset(self): - return self.model.objects.public() + return NewsItem.objects.filter( + published_at__isnull=False, + published_at__lt=timezone.now(), + archived=self.kwargs['archived'] + ) class NewsDetail(DetailView): - model = models.NewsItem + model = NewsItem template_name = 'news_detail.html' context_object_name = 'news_item' - def get_context_data(self, **kwargs): - context = super(NewsDetail, self).get_context_data(**kwargs) - news_item = self.get_object() - timed = news_item.published_at > timezone.now() - - if news_item.public and timed: - context['not_public'] = True - context['timed'] = True - elif not news_item.public: - context['not_public'] = True - context['timed'] = False - - return context - diff --git a/profiles/models.py b/profiles/models.py index c072b585..2e85ca33 100644 --- a/profiles/models.py +++ b/profiles/models.py @@ -30,3 +30,4 @@ class Profile(CreatedUpdatedModel, UUIDModel): def create_profile(sender, created, instance, **kwargs): if created: Profile.objects.create(user=instance) + diff --git a/program/__init__.py b/program/__init__.py index e69de29b..9bdc00f8 100644 --- a/program/__init__.py +++ b/program/__init__.py @@ -0,0 +1,2 @@ +default_app_config = 'program.apps.ProgramConfig' + diff --git a/program/admin.py b/program/admin.py index 5256637a..af5b07e9 100644 --- a/program/admin.py +++ b/program/admin.py @@ -1,8 +1,12 @@ from django.contrib import admin -from .models import Event, Speaker, EventType +from .models import Event, Speaker, EventType, EventInstance +@admin.register(EventInstance) +class EventInstanceAdmin(admin.ModelAdmin): + pass + @admin.register(EventType) class EventTypeAdmin(admin.ModelAdmin): pass @@ -22,17 +26,8 @@ class EventAdmin(admin.ModelAdmin): list_display = [ 'title', 'event_type', - 'get_days', - 'start', - 'end', ] - def get_days(self, obj): - return ', '.join([ - str(day.date.strftime('%a')) - for day in obj.days.all() - ]) - inlines = [ SpeakerInline ] diff --git a/program/apps.py b/program/apps.py index 42b3e8ae..f6a955ad 100644 --- a/program/apps.py +++ b/program/apps.py @@ -1,7 +1,12 @@ -from __future__ import unicode_literals - from django.apps import AppConfig - +from django.db.models.signals import m2m_changed, pre_save +from .signal_handlers import check_speaker_event_camp_consistency, check_speaker_camp_change class ProgramConfig(AppConfig): name = 'program' + + def ready(self): + from .models import Speaker + m2m_changed.connect(check_speaker_event_camp_consistency, sender=Speaker.events.through) + pre_save.connect(check_speaker_camp_change, sender=Speaker) + diff --git a/program/migrations/0010_auto_20161212_1809.py b/program/migrations/0010_auto_20161212_1809.py new file mode 100644 index 00000000..e85ea059 --- /dev/null +++ b/program/migrations/0010_auto_20161212_1809.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.4 on 2016-12-12 18:09 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('camps', '0007_auto_20161212_1803'), + ('program', '0009_auto_20160827_0752'), + ] + + operations = [ + migrations.CreateModel( + name='EventInstance', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', models.DateTimeField(auto_now_add=True)), + ('updated', models.DateTimeField(auto_now=True)), + ('start', models.DateTimeField()), + ('end', models.DateTimeField()), + ], + options={ + 'ordering': ['start'], + }, + ), + migrations.RemoveField( + model_name='event', + name='days', + ), + migrations.RemoveField( + model_name='event', + name='end', + ), + migrations.RemoveField( + model_name='event', + name='start', + ), + migrations.AddField( + model_name='event', + name='camp', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='camps.Camp'), + ), + migrations.AddField( + model_name='eventinstance', + name='event', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='instances', to='program.Event'), + ), + ] diff --git a/program/migrations/0011_auto_20161229_2149.py b/program/migrations/0011_auto_20161229_2149.py new file mode 100644 index 00000000..3dd59539 --- /dev/null +++ b/program/migrations/0011_auto_20161229_2149.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.4 on 2016-12-29 21:49 +from __future__ import unicode_literals + +import django.contrib.postgres.fields.ranges +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('program', '0010_auto_20161212_1809'), + ] + + operations = [ + migrations.AlterModelOptions( + name='eventinstance', + options={'ordering': ['when']}, + ), + migrations.RemoveField( + model_name='eventinstance', + name='end', + ), + migrations.RemoveField( + model_name='eventinstance', + name='start', + ), + migrations.AddField( + model_name='eventinstance', + name='when', + field=django.contrib.postgres.fields.ranges.DateTimeRangeField(null=True), + ), + ] diff --git a/program/migrations/0012_auto_20161229_2150.py b/program/migrations/0012_auto_20161229_2150.py new file mode 100644 index 00000000..3afafad9 --- /dev/null +++ b/program/migrations/0012_auto_20161229_2150.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.4 on 2016-12-29 21:50 +from __future__ import unicode_literals + +import django.contrib.postgres.fields.ranges +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('program', '0011_auto_20161229_2149'), + ] + + operations = [ + migrations.AlterField( + model_name='eventinstance', + name='when', + field=django.contrib.postgres.fields.ranges.DateTimeRangeField(), + ), + ] diff --git a/program/migrations/0013_auto_20170121_1312.py b/program/migrations/0013_auto_20170121_1312.py new file mode 100644 index 00000000..1784940f --- /dev/null +++ b/program/migrations/0013_auto_20170121_1312.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2017-01-21 12:12 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('program', '0012_auto_20161229_2150'), + ] + + operations = [ + migrations.AlterField( + model_name='event', + name='camp', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='events', to='camps.Camp'), + ), + ] diff --git a/program/migrations/0014_speaker_camp.py b/program/migrations/0014_speaker_camp.py new file mode 100644 index 00000000..cf77438c --- /dev/null +++ b/program/migrations/0014_speaker_camp.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2017-01-22 13:39 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('camps', '0017_remove_camp_description'), + ('program', '0013_auto_20170121_1312'), + ] + + operations = [ + migrations.AddField( + model_name='speaker', + name='camp', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='speakers', to='camps.Camp'), + ), + ] diff --git a/program/models.py b/program/models.py index fadf7442..a88521f0 100644 --- a/program/models.py +++ b/program/models.py @@ -1,9 +1,11 @@ from __future__ import unicode_literals - +from django.contrib.postgres.fields import DateTimeRangeField from django.db import models from django.utils.text import slugify - +from django.conf import settings from utils.models import CreatedUpdatedModel +from django.core.exceptions import ValidationError +from datetime import timedelta class EventType(CreatedUpdatedModel): @@ -18,33 +20,83 @@ class EventType(CreatedUpdatedModel): class Event(CreatedUpdatedModel): - """ Something that is on the program. """ + """ Something that is on the program one or more times. """ title = models.CharField(max_length=255) slug = models.SlugField(blank=True, max_length=255) abstract = models.TextField() event_type = models.ForeignKey(EventType) - days = models.ManyToManyField('camps.Day', blank=True) - start = models.TimeField(null=True, blank=True) - end = models.TimeField(null=True, blank=True) + camp = models.ForeignKey('camps.Camp', null=True, related_name="events") class Meta: ordering = ['title'] def __unicode__(self): - return self.title + return '%s (%s)' % (self.title, self.camp.title) def save(self, **kwargs): if not self.slug: self.slug = slugify(self.title) super(Event, self).save(**kwargs) + @property + def speakers_list(self): + if self.speakers.exists(): + return ", ".join(self.speakers.all().values_list('name', flat=True)) + return False + + +class EventInstance(CreatedUpdatedModel): + """ An instance of an event """ + event = models.ForeignKey('program.event', related_name='instances') + when = DateTimeRangeField() + + class Meta: + ordering = ['when'] + + def __unicode__(self): + return '%s (%s)' % (self.event, self.when) + + def __clean__(self): + errors = [] + if self.when.lower > self.when.upper: + errors.append(ValidationError({'when', "Start should be earlier than finish"})) + + if self.when.lower.time().minute != 0 and self.when.lower.time().minute != 30: + errors.append(ValidationError({'when', "Start time minute should be 0 or 30."})) + + if self.when.upper.time().minute != 0 and self.when.upper.time().minute != 30: + errors.append(ValidationError({'when', "End time minute should be 0 or 30."})) + + if errors: + raise ValidationError(errors) + + @property + def schedule_date(self): + """ + Returns the schedule date of this eventinstance. Schedule date is determined by substracting + settings.SCHEDULE_MIDNIGHT_OFFSET_HOURS from the eventinstance start time. This means that if + an event is scheduled for 00:30 wednesday evening (technically thursday) then the date + after substracting 5 hours would be wednesdays date, not thursdays. + """ + return (self.when.lower-timedelta(hours=settings.SCHEDULE_MIDNIGHT_OFFSET_HOURS)).date() + + @property + def timeslots(self): + """ + Find the number of timeslots this eventinstance takes up + """ + seconds = (self.when.upper-self.when.lower).seconds + minutes = seconds / 60 + return minutes / settings.SCHEDULE_TIMESLOT_LENGTH_MINUTES + class Speaker(CreatedUpdatedModel): - """ Person anchoring an event. """ + """ A Person anchoring an event. """ name = models.CharField(max_length=150) biography = models.TextField() picture = models.ImageField(null=True, blank=True) slug = models.SlugField(blank=True, max_length=255) + camp = models.ForeignKey('camps.Camp', null=True, related_name="speakers") events = models.ManyToManyField( Event, related_name='speakers', @@ -56,7 +108,7 @@ class Speaker(CreatedUpdatedModel): ordering = ['name'] def __unicode__(self): - return self.name + return '%s (%s)' % (self.name, self.camp) def save(self, **kwargs): if not self.slug: diff --git a/program/signal_handlers.py b/program/signal_handlers.py new file mode 100644 index 00000000..cb4be252 --- /dev/null +++ b/program/signal_handlers.py @@ -0,0 +1,16 @@ +from django.core.exceptions import ValidationError + +def check_speaker_event_camp_consistency(sender, instance, **kwargs): + if kwargs['action'] == 'pre_add': + for pk in kwargs['pk_set']: + # check if this event belongs to a different event than the speaker does + from program.models import Event + event = Event.objects.get(id=pk) + if event.camp != instance.camp: + raise ValidationError({'events': 'One or more events belong to a different camp (%s) than the speaker (%s) does' % (event.camp, instance.camp)}) + +def check_speaker_camp_change(sender, instance, **kwargs): + for event in instance.events.all(): + if event.camp != instance.camp: + raise ValidationError({'camp': 'You cannot change the camp a speaker belongs to if the speaker is associated with one or more events.'}) + diff --git a/bornhack/templates/speakers.html b/program/templates/call_for_speakers_bornhack-2016.html similarity index 100% rename from bornhack/templates/speakers.html rename to program/templates/call_for_speakers_bornhack-2016.html diff --git a/program/templates/event_list.html b/program/templates/event_list.html index 5c889534..2fe49aea 100644 --- a/program/templates/event_list.html +++ b/program/templates/event_list.html @@ -9,7 +9,7 @@
{% for event in event_list %} {% if event.event_type.name != "Facilities" %} - + {{ event.event_type.name }} diff --git a/program/templates/program_base.html b/program/templates/program_base.html index 03f61294..a223c3bf 100644 --- a/program/templates/program_base.html +++ b/program/templates/program_base.html @@ -1,19 +1,49 @@ {% extends 'schedule_base.html' %} {% block schedule_content %} +
+
+ Overview + {% for day in camp.camp_days %} + {% with day.lower.date|date:"m" as month_padded %} + {% with day.lower.date|date:"d" as day_padded %} + + {{ day.lower.date|date:"l" }} + + {% endwith %} + {% endwith %} + {% endfor %} +
+
- - Overview - -{% for day in days %} -{% with day.date|date:"m" as month_padded %} -{% with day.date|date:"d" as day_padded %} - - {{ day.date|date:"l" }} - -{% endwith %} -{% endwith %} -{% endfor %} +

+ +


diff --git a/program/templates/program_day.html b/program/templates/program_day.html index ea0b4133..b032a4c0 100644 --- a/program/templates/program_day.html +++ b/program/templates/program_day.html @@ -3,17 +3,14 @@ {% block program_content %}

{{ date|date:"l, F jS" }}

{% for event in events %} - {% ifchanged event.event_type %} - {% if not forloop.first %}
{% endif %}

{{ event.event_type }}

+ + + +
+ {% for timeslot in timeslots %} + + + {% for eventinstance in eventinstances %} + {% if eventinstance.when.lower.time == timeslot.time %} + + {% endif %} + {% endfor %} + + {% endfor %} +
{{ timeslot.time }} + + {{ eventinstance.event.title }}
+ {{ eventinstance.when.lower.time }}-{{ eventinstance.when.upper.time }} +
+
+ + {% endblock program_content %} diff --git a/program/templates/program_event_detail.html b/program/templates/program_event_detail.html index d6e71118..121f62bb 100644 --- a/program/templates/program_event_detail.html +++ b/program/templates/program_event_detail.html @@ -2,35 +2,32 @@ {% load commonmark %} {% block schedule_content %} +
+
{{ event.event_type.name }} {{ event.title }}
+
+

+ {{ event.abstract|commonmark }} + {% if event.speakers.exists %} +


+ {% for speaker in event.speakers.all %} +

{{ speaker }}

+ {{ speaker.biography|commonmark }} + {% endfor %} + {% endif %} +

-

- - {{ event.event_type.name }} - - {{ event.title }} -

- -

-{% if event.start and event.end and event.days.all.exists %} - {{ event.start|date:"H:i" }} - {{ event.end|date:"H:i" }} on - {% for day in event.days.all %}{{ day.date|date:"l" }}{% if not forloop.last %}, {% endif %}{% endfor %}
-{% else %} -Not scheduled yet -{% endif %} -

- -{{ event.abstract|commonmark }} - -
- -{% if event.speakers.exists %} -{% for speaker in event.speakers.all %} - -

{{ speaker }}

- {{ speaker.biography|commonmark }} - -{% endfor %} - -{% endif %} +
+

Instances

+
    + {% for ei in event.instances.all %} +
  • {{ ei.when.lower|date:"l M. d H:i" }} - {{ ei.when.upper|date:"H:i" }}
  • + {% empty %} + No instances scheduled yet + {% endfor %} +
+ +
+
{% endblock schedule_content %} + diff --git a/program/templates/program_overview.html b/program/templates/program_overview.html index 89e44a2d..78af268c 100644 --- a/program/templates/program_overview.html +++ b/program/templates/program_overview.html @@ -1,36 +1,28 @@ {% extends 'program_base.html' %} {% block program_content %} - - All - -{% for event_type in event_types %} - - {{ event_type.name }} - -{% endfor %} - -
- -{% for day, events in day_events.items %} - {{ day.date|date:"D d/m" }}
+{% for day in camp.camp_days %} + {{ day.lower.date|date:"D d/m" }}
- {% for event in events %} + {% for event in camp.events.all %} + {% for eventinstance in event.instances.all %} + {% if eventinstance.schedule_date == day.lower.date %} + {% if not eventtype or eventtype == eventinstance.event.event_type %} - {{ event.start|date:"H:i" }} - {{ event.end|date:"H:i" }} + href="{% url 'event_detail' camp_slug=camp.slug slug=eventinstance.event.slug %}" + style="background-color: {{ eventinstance.event.event_type.color }}; border: 0; color: {% if eveninstance.event.event_type.light_text %}white{% else %}black{% endif %};"> + {{ eventinstance.when.lower|date:"H:i" }} - {{ eventinstance.when.upper|date:"H:i" }}
{{ event }}
- {% if event.speakers.exists %} - by {{ event.speakers.all|join:", " }} - {% endif %} - + {% if event.speakers.exists %}by {{ event.speakers_list }}{% endif %}
- {% endfor %} + {% endif %} + {% endif %} + {% endfor %} + {% endfor %}

-{% endfor %} + {% endfor %} {% endblock program_content %} diff --git a/program/templates/schedule_base.html b/program/templates/schedule_base.html index f74402ce..380f4e71 100644 --- a/program/templates/schedule_base.html +++ b/program/templates/schedule_base.html @@ -1,15 +1,16 @@ {% extends 'base.html' %} {% block content %} + +

- Schedule - Call for Speakers - Speakers - Talks & Events -

- -
- {% block schedule_content %} {% endblock schedule_content %} diff --git a/program/templates/speaker_detail.html b/program/templates/speaker_detail.html index 0f4725cd..ae9de00d 100644 --- a/program/templates/speaker_detail.html +++ b/program/templates/speaker_detail.html @@ -9,23 +9,27 @@
{% if speaker.events.exists %} -{% for event in speaker.events.all %} + {% for event in speaker.events.all %} +

+ + {{ event.event_type.name }} + + {{ event.title }} +

+ {{ event.abstract|commonmark }} -

- - {{ event.event_type.name }} -
-{{ event.title }}

- {{ event.abstract|commonmark }} -{% if event.start and event.end and event.days.all.exists %} - At {{ event.start|date:"H:i" }} - {{ event.end|date:"H:i" }} on - {% for day in event.days.all %}{{ day.date|date:"l" }}{% if not forloop.last %}, {% endif %}{% endfor %}
-{% else %} -Not scheduled yet +

Instances

+
    + {% for ei in event.instances.all %} +
  • {{ ei.when.lower|date:"l M. d H:i" }} - {{ ei.when.upper|date:"H:i" }}
  • + {% empty %} + No instances scheduled yet + {% endfor %} +
+ +
+ {% empty %} + No events registered for this speaker yet + {% endfor %} {% endif %} - -{% endfor %} - -{% endif %} - {% endblock schedule_content %} diff --git a/program/templates/speaker_list.html b/program/templates/speaker_list.html index 30dfbf1a..c5cbbc40 100644 --- a/program/templates/speaker_list.html +++ b/program/templates/speaker_list.html @@ -8,7 +8,7 @@
{% for speaker in speaker_list %} - + {{ speaker.name }} ({{ speaker.events.all.count }} event{{ speaker.events.all.count|pluralize }}) {% endfor %} diff --git a/program/urls.py b/program/urls.py deleted file mode 100644 index bc311055..00000000 --- a/program/urls.py +++ /dev/null @@ -1,11 +0,0 @@ -from django.conf.urls import url -from . import views - -urlpatterns = [ - url(r'^(?P\d{4})-(?P\d{2})-(?P\d{2})/$', views.ProgramDayView.as_view(), name='day'), - url(r'^$', views.ProgramOverviewView.as_view(), name='index'), - url(r'^speakers/$', views.SpeakerListView.as_view(), name='speaker_index'), - url(r'^speakers/(?P[-_\w+]+)/$', views.SpeakerDetailView.as_view(), name='speaker_detail'), - url(r'^events/$', views.EventListView.as_view(), name='event_index'), - url(r'^(?P[-_\w+]+)/$', views.EventDetailView.as_view(), name='event'), -] diff --git a/program/views.py b/program/views.py index 01d71dc7..beb937a7 100644 --- a/program/views.py +++ b/program/views.py @@ -1,82 +1,111 @@ from collections import OrderedDict - import datetime from django.views.generic import ListView, TemplateView, DetailView - -from camps.models import Day +from camps.mixins import CampViewMixin from . import models +from django.http import Http404 +import datetime +from django.conf import settings -class SpeakerDetailView(DetailView): +class SpeakerDetailView(CampViewMixin, DetailView): model = models.Speaker template_name = 'speaker_detail.html' -class SpeakerListView(ListView): + +class SpeakerListView(CampViewMixin, ListView): model = models.Speaker template_name = 'speaker_list.html' -class EventListView(ListView): + def get_queryset(self, *args, **kwargs): + return models.Speaker.objects.filter(camp=self.camp) + + +class EventListView(CampViewMixin, ListView): model = models.Event template_name = 'event_list.html' -class ProgramOverviewView(ListView): + +class ProgramOverviewView(CampViewMixin, ListView): model = models.Event template_name = 'program_overview.html' - def get_context_data(self, **kwargs): - context = super( - ProgramOverviewView, self - ).get_context_data(**kwargs) - - days = Day.objects.all() - context['days'] = days - - filter = {} + def dispatch(self, *args, **kwargs): + """ If an event type has been supplied check if it is valid """ if 'type' in self.request.GET: - event_type = self.request.GET['type'] - filter["event_type__slug"] = event_type - - context['day_events'] = OrderedDict([ - ( - day, - self.get_queryset().filter( - days__in=[day], - **filter - ).order_by( - 'start' + try: + eventtype = models.EventType.objects.get( + slug=self.request.GET['type'] ) - ) - for day in days - ]) - - context['event_types'] = models.EventType.objects.all() + except models.EventType.DoesNotExist: + raise Http404 + return super(ProgramOverviewView, self).dispatch(*args, **kwargs) + def get_context_data(self, *args, **kwargs): + context = super(ProgramOverviewView, self).get_context_data(**kwargs) + if 'type' in self.request.GET: + context['eventtype'] = models.EventType.objects.get(slug=self.request.GET['type']) return context -class ProgramDayView(TemplateView): +class ProgramDayView(CampViewMixin, TemplateView): template_name = 'program_day.html' + def dispatch(self, *args, **kwargs): + """ If an event type has been supplied check if it is valid """ + if 'type' in self.request.GET: + try: + eventtype = models.EventType.objects.get( + slug=self.request.GET['type'] + ) + except models.EventType.DoesNotExist: + raise Http404 + return super(ProgramDayView, self).dispatch(*args, **kwargs) - def get_context_data(self, **kwargs): + def get_context_data(self, *args, **kwargs): context = super(ProgramDayView, self).get_context_data(**kwargs) - year = int(kwargs['year']) - month = int(kwargs['month']) - day = int(kwargs['day']) - date = datetime.date(year=year, month=month, day=day) - day = Day.objects.filter(date=date) - context['date'] = date - context['events'] = models.Event.objects.filter(days=day).order_by('event_type', 'start') - context['event_types'] = models.EventType.objects.all() - context['days'] = Day.objects.filter(date__year=year) + when = datetime.datetime(year=int(self.kwargs['year']), month=int(self.kwargs['month']), day=int(self.kwargs['day'])) + eventinstances = models.EventInstance.objects.filter(event__in=self.camp.events.all()) + skip = [] + for ei in eventinstances: + if ei.schedule_date != when.date(): + skip.append(ei.id) + else: + if 'type' in self.request.GET: + eventtype = models.EventType.objects.get( + slug=self.request.GET['type'] + ) + if ei.event.event_type != eventtype: + skip.append(ei.id) + context['eventinstances'] = eventinstances.exclude(id__in=skip).order_by('event__event_type') + + start = when + datetime.timedelta(hours=settings.SCHEDULE_MIDNIGHT_OFFSET_HOURS) + timeslots = [] + # calculate how many timeslots we have in the schedule based on the lenght of the timeslots in minutes, + # and the number of minutes in 24 hours + for i in range(0,(24*60)/settings.SCHEDULE_TIMESLOT_LENGTH_MINUTES): + timeslot = start + datetime.timedelta(minutes=i*settings.SCHEDULE_TIMESLOT_LENGTH_MINUTES) + timeslots.append(timeslot) + context['timeslots'] = timeslots + + # include the components to make the urls + context['urlyear'] = self.kwargs['year'] + context['urlmonth'] = self.kwargs['month'] + context['urlday'] = self.kwargs['day'] + + if 'type' in self.request.GET: + context['eventtype'] = models.EventType.objects.get(slug=self.request.GET['type']) + return context -class EventDetailView(DetailView): + +class EventDetailView(CampViewMixin, DetailView): model = models.Event template_name = 'program_event_detail.html' - def get_context_data(self, **kwargs): - context = super(EventDetailView, self).get_context_data(**kwargs) - # TODO: date__year is hardcoded here - need fix for 2017 :P - context['days'] = Day.objects.filter(date__year=2016) - return context + +class CallForSpeakersView(CampViewMixin, TemplateView): + def get_template_names(self): + return 'call_for_speakers_%s.html' % self.camp.slug + + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..e7d4f986 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,27 @@ +CommonMark==0.7.3 +Django==1.10.5 +Pillow==4.0.0 +PyPDF2==1.26.0 +Unidecode==0.04.20 +argparse==1.2.1 +bleach==1.5.0 +django-allauth==0.30.0 +django-bleach==0.3.0 +django-bootstrap3==8.1.0 +django-debug-toolbar==1.6 +django-environ==0.4.1 +django-wkhtmltopdf==3.1.0 +future==0.16.0 +html5lib==0.9999999 +oauthlib==2.0.1 +olefile==0.44 +psycopg2==2.6.2 +python-openid==2.2.5 +pytz==2016.10 +qrcode==5.3 +requests==2.12.5 +requests-oauthlib==0.7.0 +six==1.10.0 +sqlparse==0.2.2 +webencodings==0.5 +wsgiref==0.1.2 diff --git a/requirements/base.txt b/requirements/base.txt deleted file mode 100644 index 6efaf098..00000000 --- a/requirements/base.txt +++ /dev/null @@ -1,12 +0,0 @@ -django>=1.9 -django-allauth>=0.23.0 -django-bootstrap3>=7.0.0 -django-environ>=0.4.0 -psycopg2>=2.6.1 -PyPDF2>=1.25.1 -django-wkhtmltopdf>=3.0.0 -Pillow==3.2.0 -qrcode==5.3 -CommonMark==0.6.4 -django-bleach==0.3.0 -Unidecode==0.4.19 \ No newline at end of file diff --git a/requirements/development.txt b/requirements/development.txt deleted file mode 100644 index a4481248..00000000 --- a/requirements/development.txt +++ /dev/null @@ -1,2 +0,0 @@ --r base.txt -django-debug-toolbar==1.3.2 diff --git a/requirements/production.txt b/requirements/production.txt deleted file mode 100644 index 9c9dec9e..00000000 --- a/requirements/production.txt +++ /dev/null @@ -1 +0,0 @@ --r base.txt \ No newline at end of file diff --git a/shop/admin.py b/shop/admin.py index bb1a73a2..4675c053 100644 --- a/shop/admin.py +++ b/shop/admin.py @@ -58,7 +58,6 @@ class OrderAdmin(admin.ModelAdmin): ] list_filter = [ - 'camp', 'payment_method', 'open', 'paid', diff --git a/shop/context_processors.py b/shop/context_processors.py index 9e1e27dc..0f55fba9 100644 --- a/shop/context_processors.py +++ b/shop/context_processors.py @@ -20,3 +20,4 @@ def user_has_tickets(request): ).exists(): has_tickets = True return {'has_tickets': has_tickets} + diff --git a/shop/migrations/0033_auto_20161212_1756.py b/shop/migrations/0033_auto_20161212_1756.py new file mode 100644 index 00000000..527c5c88 --- /dev/null +++ b/shop/migrations/0033_auto_20161212_1756.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.4 on 2016-12-12 17:56 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('shop', '0032_order_customer_comment'), + ] + + operations = [ + migrations.RemoveField( + model_name='customorder', + name='camp', + ), + migrations.RemoveField( + model_name='order', + name='camp', + ), + ] diff --git a/shop/models.py b/shop/models.py index 56f56bd9..a22c7c4f 100644 --- a/shop/models.py +++ b/shop/models.py @@ -17,12 +17,6 @@ from unidecode import unidecode class CustomOrder(CreatedUpdatedModel): - camp = models.ForeignKey( - 'camps.Camp', - verbose_name=_('Camp'), - help_text=_('The camp this custom order is for.'), - ) - text = models.TextField( help_text=_('The invoice text') ) @@ -78,12 +72,6 @@ class Order(CreatedUpdatedModel): default=True, ) - camp = models.ForeignKey( - 'camps.Camp', - verbose_name=_('Camp'), - help_text=_('The camp this shop order is for.'), - ) - CREDIT_CARD = 'credit_card' BLOCKCHAIN = 'blockchain' BANK_TRANSFER = 'bank_transfer' @@ -168,7 +156,7 @@ class Order(CreatedUpdatedModel): @property def description(self): - return "BornHack %s order #%s" % (self.camp.start.year, self.pk) + return "Order #%s" % self.pk def get_absolute_url(self): return str(reverse_lazy('shop:order_detail', kwargs={'pk': self.pk})) diff --git a/shop/views.py b/shop/views.py index 89daf2b4..1c6583fe 100644 --- a/shop/views.py +++ b/shop/views.py @@ -20,7 +20,6 @@ from django.views.decorators.csrf import csrf_exempt from django.utils.dateparse import parse_datetime from django.utils import timezone -from camps.models import Camp from shop.models import ( Order, Product, @@ -234,7 +233,6 @@ class ProductDetailView(FormView, DetailView): # no open order - open a new one order = Order.objects.create( user=self.request.user, - camp=Camp.objects.current() ) # get product from kwargs diff --git a/sponsors/__init__.py b/sponsors/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sponsors/admin.py b/sponsors/admin.py new file mode 100644 index 00000000..8c38f3f3 --- /dev/null +++ b/sponsors/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/sponsors/apps.py b/sponsors/apps.py new file mode 100644 index 00000000..85079993 --- /dev/null +++ b/sponsors/apps.py @@ -0,0 +1,7 @@ +from __future__ import unicode_literals + +from django.apps import AppConfig + + +class SponsorsConfig(AppConfig): + name = 'sponsors' diff --git a/sponsors/migrations/__init__.py b/sponsors/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sponsors/models.py b/sponsors/models.py new file mode 100644 index 00000000..bd4b2abe --- /dev/null +++ b/sponsors/models.py @@ -0,0 +1,5 @@ +from __future__ import unicode_literals + +from django.db import models + +# Create your models here. diff --git a/bornhack/templates/sponsors.html b/sponsors/templates/bornhack-2016-sponsors.html similarity index 95% rename from bornhack/templates/sponsors.html rename to sponsors/templates/bornhack-2016-sponsors.html index 47afb755..a0175b1e 100644 --- a/bornhack/templates/sponsors.html +++ b/sponsors/templates/bornhack-2016-sponsors.html @@ -6,11 +6,10 @@ Call for Sponsors | {{ block.super }} {% endblock %} {% block content %} -

Our Sponsors

+

BornHack 2016 Sponsors

- This is an alphabetical list of our current sponsors. We are immensely - grateful for all the help we are getting, and we are looking forward to - adding more names to this list. + This is an alphabetical list of the BornHack 2016 sponsors. An event like BornHack can not be built on hard work and + good intentions alone - it would simply not have been possible without the financial help from these organisations. Thank you, we are immensely grateful!


@@ -116,7 +115,7 @@ Call for Sponsors | {{ block.super }} tyktech logo - Badges, lanyards, wristbands + Badges, wristbands


diff --git a/sponsors/tests.py b/sponsors/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/sponsors/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/sponsors/views.py b/sponsors/views.py new file mode 100644 index 00000000..d8565fe0 --- /dev/null +++ b/sponsors/views.py @@ -0,0 +1,9 @@ +from django.views.generic import TemplateView +from camps.mixins import CampViewMixin + + +class SponsorView(CampViewMixin, TemplateView): + def get_template_names(self): + return '%s-sponsors.html' % self.camp.slug + + diff --git a/bornhack/static_src/css/bootstrap-theme.css b/static_src/css/bootstrap-theme.css similarity index 100% rename from bornhack/static_src/css/bootstrap-theme.css rename to static_src/css/bootstrap-theme.css diff --git a/bornhack/static_src/css/bootstrap-theme.css.map b/static_src/css/bootstrap-theme.css.map similarity index 100% rename from bornhack/static_src/css/bootstrap-theme.css.map rename to static_src/css/bootstrap-theme.css.map diff --git a/bornhack/static_src/css/bootstrap-theme.min.css b/static_src/css/bootstrap-theme.min.css similarity index 100% rename from bornhack/static_src/css/bootstrap-theme.min.css rename to static_src/css/bootstrap-theme.min.css diff --git a/bornhack/static_src/css/bootstrap.css b/static_src/css/bootstrap.css similarity index 100% rename from bornhack/static_src/css/bootstrap.css rename to static_src/css/bootstrap.css diff --git a/bornhack/static_src/css/bootstrap.css.map b/static_src/css/bootstrap.css.map similarity index 100% rename from bornhack/static_src/css/bootstrap.css.map rename to static_src/css/bootstrap.css.map diff --git a/bornhack/static_src/css/bootstrap.min.css b/static_src/css/bootstrap.min.css similarity index 100% rename from bornhack/static_src/css/bootstrap.min.css rename to static_src/css/bootstrap.min.css diff --git a/bornhack/static_src/css/bornhack.css b/static_src/css/bornhack.css similarity index 80% rename from bornhack/static_src/css/bornhack.css rename to static_src/css/bornhack.css index dbb1fbff..cf2e9e21 100644 --- a/bornhack/static_src/css/bornhack.css +++ b/static_src/css/bornhack.css @@ -1,5 +1,5 @@ body { - margin-top: 90px; + margin-top: 85px; margin-bottom: 35px; } @@ -42,23 +42,7 @@ a, a:active, a:focus { } .navbar-fixed-top { - min-height: 80px; -} - -@media (min-width: 768px) { - .nav li a { - padding: 30px 15px; - } - .nav { - float: right!important; - } -} - -@media (max-width: 767px) { - .nav li { - text-align: center; - font-size: 20px; - } + min-height: 70px; } .navbar-toggle .icon-bar { @@ -121,13 +105,8 @@ footer { } .event { - max-width: 200px; width: 200px; - height: 150px; - display: inline-block; - margin: 5px 5px; padding: 5px; - flex: 1 1 auto; } .event:hover { @@ -135,3 +114,4 @@ footer { color: white !important; text-decoration: none; } + diff --git a/bornhack/static_src/css/leaflet.css b/static_src/css/leaflet.css similarity index 100% rename from bornhack/static_src/css/leaflet.css rename to static_src/css/leaflet.css diff --git a/bornhack/static_src/fonts/glyphicons-halflings-regular.eot b/static_src/fonts/glyphicons-halflings-regular.eot similarity index 100% rename from bornhack/static_src/fonts/glyphicons-halflings-regular.eot rename to static_src/fonts/glyphicons-halflings-regular.eot diff --git a/bornhack/static_src/fonts/glyphicons-halflings-regular.svg b/static_src/fonts/glyphicons-halflings-regular.svg similarity index 100% rename from bornhack/static_src/fonts/glyphicons-halflings-regular.svg rename to static_src/fonts/glyphicons-halflings-regular.svg diff --git a/bornhack/static_src/fonts/glyphicons-halflings-regular.ttf b/static_src/fonts/glyphicons-halflings-regular.ttf similarity index 100% rename from bornhack/static_src/fonts/glyphicons-halflings-regular.ttf rename to static_src/fonts/glyphicons-halflings-regular.ttf diff --git a/bornhack/static_src/fonts/glyphicons-halflings-regular.woff b/static_src/fonts/glyphicons-halflings-regular.woff similarity index 100% rename from bornhack/static_src/fonts/glyphicons-halflings-regular.woff rename to static_src/fonts/glyphicons-halflings-regular.woff diff --git a/bornhack/static_src/fonts/glyphicons-halflings-regular.woff2 b/static_src/fonts/glyphicons-halflings-regular.woff2 similarity index 100% rename from bornhack/static_src/fonts/glyphicons-halflings-regular.woff2 rename to static_src/fonts/glyphicons-halflings-regular.woff2 diff --git a/static_src/img/bornhack-2016/ahf/jarlsgaard.jpg b/static_src/img/bornhack-2016/ahf/jarlsgaard.jpg new file mode 100644 index 00000000..aea7c3df Binary files /dev/null and b/static_src/img/bornhack-2016/ahf/jarlsgaard.jpg differ diff --git a/static_src/img/bornhack-2016/ahf/thumbnail_jarlsgaard.jpg.png b/static_src/img/bornhack-2016/ahf/thumbnail_jarlsgaard.jpg.png new file mode 100644 index 00000000..1f3594b4 Binary files /dev/null and b/static_src/img/bornhack-2016/ahf/thumbnail_jarlsgaard.jpg.png differ diff --git a/static_src/img/bornhack-2016/esbjerg/1600x1000-B12A2398.jpg b/static_src/img/bornhack-2016/esbjerg/1600x1000-B12A2398.jpg new file mode 100644 index 00000000..1bd1a630 Binary files /dev/null and b/static_src/img/bornhack-2016/esbjerg/1600x1000-B12A2398.jpg differ diff --git a/static_src/img/bornhack-2016/esbjerg/1600x900-B12A2452.jpg b/static_src/img/bornhack-2016/esbjerg/1600x900-B12A2452.jpg new file mode 100644 index 00000000..d8992840 Binary files /dev/null and b/static_src/img/bornhack-2016/esbjerg/1600x900-B12A2452.jpg differ diff --git a/static_src/img/bornhack-2016/esbjerg/1600x900-B12A2485.jpg b/static_src/img/bornhack-2016/esbjerg/1600x900-B12A2485.jpg new file mode 100644 index 00000000..ff21e77f Binary files /dev/null and b/static_src/img/bornhack-2016/esbjerg/1600x900-B12A2485.jpg differ diff --git a/static_src/img/bornhack-2016/esbjerg/1600x900-B12A2514.jpg b/static_src/img/bornhack-2016/esbjerg/1600x900-B12A2514.jpg new file mode 100644 index 00000000..022a0fe1 Binary files /dev/null and b/static_src/img/bornhack-2016/esbjerg/1600x900-B12A2514.jpg differ diff --git a/static_src/img/bornhack-2016/esbjerg/1600x900-B12A2604.jpg b/static_src/img/bornhack-2016/esbjerg/1600x900-B12A2604.jpg new file mode 100644 index 00000000..5ca07ff7 Binary files /dev/null and b/static_src/img/bornhack-2016/esbjerg/1600x900-B12A2604.jpg differ diff --git a/static_src/img/bornhack-2016/esbjerg/1600x900-B12A2608.jpg b/static_src/img/bornhack-2016/esbjerg/1600x900-B12A2608.jpg new file mode 100644 index 00000000..b370b746 Binary files /dev/null and b/static_src/img/bornhack-2016/esbjerg/1600x900-B12A2608.jpg differ diff --git a/static_src/img/bornhack-2016/esbjerg/1600x988-B12A2610.jpg b/static_src/img/bornhack-2016/esbjerg/1600x988-B12A2610.jpg new file mode 100644 index 00000000..6cd3fd80 Binary files /dev/null and b/static_src/img/bornhack-2016/esbjerg/1600x988-B12A2610.jpg differ diff --git a/static_src/img/bornhack-2016/esbjerg/1600x988-B12A2612.jpg b/static_src/img/bornhack-2016/esbjerg/1600x988-B12A2612.jpg new file mode 100644 index 00000000..68bb1c1f Binary files /dev/null and b/static_src/img/bornhack-2016/esbjerg/1600x988-B12A2612.jpg differ diff --git a/static_src/img/bornhack-2016/esbjerg/1600x988-B12A2620.jpg b/static_src/img/bornhack-2016/esbjerg/1600x988-B12A2620.jpg new file mode 100644 index 00000000..8a12f615 Binary files /dev/null and b/static_src/img/bornhack-2016/esbjerg/1600x988-B12A2620.jpg differ diff --git a/static_src/img/bornhack-2016/esbjerg/1600x988-B12A2624.jpg b/static_src/img/bornhack-2016/esbjerg/1600x988-B12A2624.jpg new file mode 100644 index 00000000..3a1ba159 Binary files /dev/null and b/static_src/img/bornhack-2016/esbjerg/1600x988-B12A2624.jpg differ diff --git a/static_src/img/bornhack-2016/esbjerg/1600x988-B12A2631.jpg b/static_src/img/bornhack-2016/esbjerg/1600x988-B12A2631.jpg new file mode 100644 index 00000000..9510dc07 Binary files /dev/null and b/static_src/img/bornhack-2016/esbjerg/1600x988-B12A2631.jpg differ diff --git a/static_src/img/bornhack-2016/esbjerg/1600x988-B12A2634.jpg b/static_src/img/bornhack-2016/esbjerg/1600x988-B12A2634.jpg new file mode 100644 index 00000000..5bb0237d Binary files /dev/null and b/static_src/img/bornhack-2016/esbjerg/1600x988-B12A2634.jpg differ diff --git a/static_src/img/bornhack-2016/esbjerg/thumbnail_1600x1000-B12A2398.jpg.png b/static_src/img/bornhack-2016/esbjerg/thumbnail_1600x1000-B12A2398.jpg.png new file mode 100644 index 00000000..c02a44ad Binary files /dev/null and b/static_src/img/bornhack-2016/esbjerg/thumbnail_1600x1000-B12A2398.jpg.png differ diff --git a/static_src/img/bornhack-2016/esbjerg/thumbnail_1600x900-B12A2452.jpg.png b/static_src/img/bornhack-2016/esbjerg/thumbnail_1600x900-B12A2452.jpg.png new file mode 100644 index 00000000..927e5bed Binary files /dev/null and b/static_src/img/bornhack-2016/esbjerg/thumbnail_1600x900-B12A2452.jpg.png differ diff --git a/static_src/img/bornhack-2016/esbjerg/thumbnail_1600x900-B12A2485.jpg.png b/static_src/img/bornhack-2016/esbjerg/thumbnail_1600x900-B12A2485.jpg.png new file mode 100644 index 00000000..7fe151f3 Binary files /dev/null and b/static_src/img/bornhack-2016/esbjerg/thumbnail_1600x900-B12A2485.jpg.png differ diff --git a/static_src/img/bornhack-2016/esbjerg/thumbnail_1600x900-B12A2514.jpg.png b/static_src/img/bornhack-2016/esbjerg/thumbnail_1600x900-B12A2514.jpg.png new file mode 100644 index 00000000..17c49c34 Binary files /dev/null and b/static_src/img/bornhack-2016/esbjerg/thumbnail_1600x900-B12A2514.jpg.png differ diff --git a/static_src/img/bornhack-2016/esbjerg/thumbnail_1600x900-B12A2604.jpg.png b/static_src/img/bornhack-2016/esbjerg/thumbnail_1600x900-B12A2604.jpg.png new file mode 100644 index 00000000..22da8f2f Binary files /dev/null and b/static_src/img/bornhack-2016/esbjerg/thumbnail_1600x900-B12A2604.jpg.png differ diff --git a/static_src/img/bornhack-2016/esbjerg/thumbnail_1600x900-B12A2608.jpg.png b/static_src/img/bornhack-2016/esbjerg/thumbnail_1600x900-B12A2608.jpg.png new file mode 100644 index 00000000..673b5ea4 Binary files /dev/null and b/static_src/img/bornhack-2016/esbjerg/thumbnail_1600x900-B12A2608.jpg.png differ diff --git a/static_src/img/bornhack-2016/esbjerg/thumbnail_1600x988-B12A2610.jpg.png b/static_src/img/bornhack-2016/esbjerg/thumbnail_1600x988-B12A2610.jpg.png new file mode 100644 index 00000000..ec7aca37 Binary files /dev/null and b/static_src/img/bornhack-2016/esbjerg/thumbnail_1600x988-B12A2610.jpg.png differ diff --git a/static_src/img/bornhack-2016/esbjerg/thumbnail_1600x988-B12A2612.jpg.png b/static_src/img/bornhack-2016/esbjerg/thumbnail_1600x988-B12A2612.jpg.png new file mode 100644 index 00000000..af45d0d7 Binary files /dev/null and b/static_src/img/bornhack-2016/esbjerg/thumbnail_1600x988-B12A2612.jpg.png differ diff --git a/static_src/img/bornhack-2016/esbjerg/thumbnail_1600x988-B12A2620.jpg.png b/static_src/img/bornhack-2016/esbjerg/thumbnail_1600x988-B12A2620.jpg.png new file mode 100644 index 00000000..7940ce36 Binary files /dev/null and b/static_src/img/bornhack-2016/esbjerg/thumbnail_1600x988-B12A2620.jpg.png differ diff --git a/static_src/img/bornhack-2016/esbjerg/thumbnail_1600x988-B12A2624.jpg.png b/static_src/img/bornhack-2016/esbjerg/thumbnail_1600x988-B12A2624.jpg.png new file mode 100644 index 00000000..2b4d3337 Binary files /dev/null and b/static_src/img/bornhack-2016/esbjerg/thumbnail_1600x988-B12A2624.jpg.png differ diff --git a/static_src/img/bornhack-2016/esbjerg/thumbnail_1600x988-B12A2631.jpg.png b/static_src/img/bornhack-2016/esbjerg/thumbnail_1600x988-B12A2631.jpg.png new file mode 100644 index 00000000..9ad74cc2 Binary files /dev/null and b/static_src/img/bornhack-2016/esbjerg/thumbnail_1600x988-B12A2631.jpg.png differ diff --git a/static_src/img/bornhack-2016/esbjerg/thumbnail_1600x988-B12A2634.jpg.png b/static_src/img/bornhack-2016/esbjerg/thumbnail_1600x988-B12A2634.jpg.png new file mode 100644 index 00000000..d067eca0 Binary files /dev/null and b/static_src/img/bornhack-2016/esbjerg/thumbnail_1600x988-B12A2634.jpg.png differ diff --git a/static_src/img/bornhack-2016/fonsmark/FA0_1961.JPG b/static_src/img/bornhack-2016/fonsmark/FA0_1961.JPG new file mode 100644 index 00000000..3021abd6 Binary files /dev/null and b/static_src/img/bornhack-2016/fonsmark/FA0_1961.JPG differ diff --git a/static_src/img/bornhack-2016/fonsmark/FA0_1983.JPG b/static_src/img/bornhack-2016/fonsmark/FA0_1983.JPG new file mode 100644 index 00000000..0d350076 Binary files /dev/null and b/static_src/img/bornhack-2016/fonsmark/FA0_1983.JPG differ diff --git a/static_src/img/bornhack-2016/fonsmark/FA0_1986.JPG b/static_src/img/bornhack-2016/fonsmark/FA0_1986.JPG new file mode 100644 index 00000000..b75c1743 Binary files /dev/null and b/static_src/img/bornhack-2016/fonsmark/FA0_1986.JPG differ diff --git a/static_src/img/bornhack-2016/fonsmark/FB1_5090.JPG b/static_src/img/bornhack-2016/fonsmark/FB1_5090.JPG new file mode 100644 index 00000000..bb106995 Binary files /dev/null and b/static_src/img/bornhack-2016/fonsmark/FB1_5090.JPG differ diff --git a/static_src/img/bornhack-2016/fonsmark/FB1_5111.JPG b/static_src/img/bornhack-2016/fonsmark/FB1_5111.JPG new file mode 100644 index 00000000..a20b19f6 Binary files /dev/null and b/static_src/img/bornhack-2016/fonsmark/FB1_5111.JPG differ diff --git a/static_src/img/bornhack-2016/fonsmark/FB1_5126.JPG b/static_src/img/bornhack-2016/fonsmark/FB1_5126.JPG new file mode 100644 index 00000000..f896ced9 Binary files /dev/null and b/static_src/img/bornhack-2016/fonsmark/FB1_5126.JPG differ diff --git a/static_src/img/bornhack-2016/fonsmark/FB1_5128.JPG b/static_src/img/bornhack-2016/fonsmark/FB1_5128.JPG new file mode 100644 index 00000000..a8500095 Binary files /dev/null and b/static_src/img/bornhack-2016/fonsmark/FB1_5128.JPG differ diff --git a/static_src/img/bornhack-2016/fonsmark/FB1_5149.JPG b/static_src/img/bornhack-2016/fonsmark/FB1_5149.JPG new file mode 100644 index 00000000..0af45b58 Binary files /dev/null and b/static_src/img/bornhack-2016/fonsmark/FB1_5149.JPG differ diff --git a/static_src/img/bornhack-2016/fonsmark/FB1_5168.JPG b/static_src/img/bornhack-2016/fonsmark/FB1_5168.JPG new file mode 100644 index 00000000..43cae016 Binary files /dev/null and b/static_src/img/bornhack-2016/fonsmark/FB1_5168.JPG differ diff --git a/static_src/img/bornhack-2016/fonsmark/FB1_5265.JPG b/static_src/img/bornhack-2016/fonsmark/FB1_5265.JPG new file mode 100644 index 00000000..a1c9c852 Binary files /dev/null and b/static_src/img/bornhack-2016/fonsmark/FB1_5265.JPG differ diff --git a/static_src/img/bornhack-2016/fonsmark/FB1_5312.JPG b/static_src/img/bornhack-2016/fonsmark/FB1_5312.JPG new file mode 100644 index 00000000..a0751d78 Binary files /dev/null and b/static_src/img/bornhack-2016/fonsmark/FB1_5312.JPG differ diff --git a/static_src/img/bornhack-2016/fonsmark/thumbnail_FA0_1961.JPG.png b/static_src/img/bornhack-2016/fonsmark/thumbnail_FA0_1961.JPG.png new file mode 100644 index 00000000..4bcd2977 Binary files /dev/null and b/static_src/img/bornhack-2016/fonsmark/thumbnail_FA0_1961.JPG.png differ diff --git a/static_src/img/bornhack-2016/fonsmark/thumbnail_FA0_1983.JPG.png b/static_src/img/bornhack-2016/fonsmark/thumbnail_FA0_1983.JPG.png new file mode 100644 index 00000000..b737c7e1 Binary files /dev/null and b/static_src/img/bornhack-2016/fonsmark/thumbnail_FA0_1983.JPG.png differ diff --git a/static_src/img/bornhack-2016/fonsmark/thumbnail_FA0_1986.JPG.png b/static_src/img/bornhack-2016/fonsmark/thumbnail_FA0_1986.JPG.png new file mode 100644 index 00000000..823dbcec Binary files /dev/null and b/static_src/img/bornhack-2016/fonsmark/thumbnail_FA0_1986.JPG.png differ diff --git a/static_src/img/bornhack-2016/fonsmark/thumbnail_FB1_5090.JPG.png b/static_src/img/bornhack-2016/fonsmark/thumbnail_FB1_5090.JPG.png new file mode 100644 index 00000000..fd9ca58c Binary files /dev/null and b/static_src/img/bornhack-2016/fonsmark/thumbnail_FB1_5090.JPG.png differ diff --git a/static_src/img/bornhack-2016/fonsmark/thumbnail_FB1_5111.JPG.png b/static_src/img/bornhack-2016/fonsmark/thumbnail_FB1_5111.JPG.png new file mode 100644 index 00000000..3b47fd78 Binary files /dev/null and b/static_src/img/bornhack-2016/fonsmark/thumbnail_FB1_5111.JPG.png differ diff --git a/static_src/img/bornhack-2016/fonsmark/thumbnail_FB1_5126.JPG.png b/static_src/img/bornhack-2016/fonsmark/thumbnail_FB1_5126.JPG.png new file mode 100644 index 00000000..ccfdd511 Binary files /dev/null and b/static_src/img/bornhack-2016/fonsmark/thumbnail_FB1_5126.JPG.png differ diff --git a/static_src/img/bornhack-2016/fonsmark/thumbnail_FB1_5128.JPG.png b/static_src/img/bornhack-2016/fonsmark/thumbnail_FB1_5128.JPG.png new file mode 100644 index 00000000..0591c39e Binary files /dev/null and b/static_src/img/bornhack-2016/fonsmark/thumbnail_FB1_5128.JPG.png differ diff --git a/static_src/img/bornhack-2016/fonsmark/thumbnail_FB1_5149.JPG.png b/static_src/img/bornhack-2016/fonsmark/thumbnail_FB1_5149.JPG.png new file mode 100644 index 00000000..a4c0ccc3 Binary files /dev/null and b/static_src/img/bornhack-2016/fonsmark/thumbnail_FB1_5149.JPG.png differ diff --git a/static_src/img/bornhack-2016/fonsmark/thumbnail_FB1_5168.JPG.png b/static_src/img/bornhack-2016/fonsmark/thumbnail_FB1_5168.JPG.png new file mode 100644 index 00000000..5f70dc98 Binary files /dev/null and b/static_src/img/bornhack-2016/fonsmark/thumbnail_FB1_5168.JPG.png differ diff --git a/static_src/img/bornhack-2016/fonsmark/thumbnail_FB1_5265.JPG.png b/static_src/img/bornhack-2016/fonsmark/thumbnail_FB1_5265.JPG.png new file mode 100644 index 00000000..52aaaddf Binary files /dev/null and b/static_src/img/bornhack-2016/fonsmark/thumbnail_FB1_5265.JPG.png differ diff --git a/static_src/img/bornhack-2016/fonsmark/thumbnail_FB1_5312.JPG.png b/static_src/img/bornhack-2016/fonsmark/thumbnail_FB1_5312.JPG.png new file mode 100644 index 00000000..92a3b857 Binary files /dev/null and b/static_src/img/bornhack-2016/fonsmark/thumbnail_FB1_5312.JPG.png differ diff --git a/bornhack/static_src/img/logo.png b/static_src/img/bornhack-2016/logo/bornhack-2016-logo-large.png similarity index 100% rename from bornhack/static_src/img/logo.png rename to static_src/img/bornhack-2016/logo/bornhack-2016-logo-large.png diff --git a/bornhack/static_src/img/logo-new.png b/static_src/img/bornhack-2016/logo/bornhack-2016-logo-small.png similarity index 100% rename from bornhack/static_src/img/logo-new.png rename to static_src/img/bornhack-2016/logo/bornhack-2016-logo-small.png diff --git a/bornhack/static_src/img/logo-new-paths.svg b/static_src/img/bornhack-2016/logo/logo-new-paths.svg similarity index 100% rename from bornhack/static_src/img/logo-new-paths.svg rename to static_src/img/bornhack-2016/logo/logo-new-paths.svg diff --git a/static_src/img/bornhack-2016/logo/logo-new.png b/static_src/img/bornhack-2016/logo/logo-new.png new file mode 100644 index 00000000..ede32e9e Binary files /dev/null and b/static_src/img/bornhack-2016/logo/logo-new.png differ diff --git a/bornhack/static_src/img/logo-new.svg b/static_src/img/bornhack-2016/logo/logo-new.svg similarity index 100% rename from bornhack/static_src/img/logo-new.svg rename to static_src/img/bornhack-2016/logo/logo-new.svg diff --git a/static_src/img/bornhack-2016/logo/logo.png b/static_src/img/bornhack-2016/logo/logo.png new file mode 100644 index 00000000..3a503c33 Binary files /dev/null and b/static_src/img/bornhack-2016/logo/logo.png differ diff --git a/bornhack/static_src/img/logo.svg b/static_src/img/bornhack-2016/logo/logo.svg similarity index 100% rename from bornhack/static_src/img/logo.svg rename to static_src/img/bornhack-2016/logo/logo.svg diff --git a/static_src/img/logo-large.png b/static_src/img/logo-large.png new file mode 100644 index 00000000..654217d7 Binary files /dev/null and b/static_src/img/logo-large.png differ diff --git a/static_src/img/logo-small.png b/static_src/img/logo-small.png new file mode 100644 index 00000000..1529b2d6 Binary files /dev/null and b/static_src/img/logo-small.png differ diff --git a/bornhack/static_src/img/sponsors/CSIS_PRI_LOGO_TURQUOISE_RGB.jpg b/static_src/img/sponsors/CSIS_PRI_LOGO_TURQUOISE_RGB.jpg similarity index 100% rename from bornhack/static_src/img/sponsors/CSIS_PRI_LOGO_TURQUOISE_RGB.jpg rename to static_src/img/sponsors/CSIS_PRI_LOGO_TURQUOISE_RGB.jpg diff --git a/bornhack/static_src/img/sponsors/DKUUGlogo.jpeg b/static_src/img/sponsors/DKUUGlogo.jpeg similarity index 100% rename from bornhack/static_src/img/sponsors/DKUUGlogo.jpeg rename to static_src/img/sponsors/DKUUGlogo.jpeg diff --git a/bornhack/static_src/img/sponsors/KristiansKaffe_Logo_Hires1.0.jpg b/static_src/img/sponsors/KristiansKaffe_Logo_Hires1.0.jpg similarity index 100% rename from bornhack/static_src/img/sponsors/KristiansKaffe_Logo_Hires1.0.jpg rename to static_src/img/sponsors/KristiansKaffe_Logo_Hires1.0.jpg diff --git a/bornhack/static_src/img/sponsors/PROSA-logo.png b/static_src/img/sponsors/PROSA-logo.png similarity index 100% rename from bornhack/static_src/img/sponsors/PROSA-logo.png rename to static_src/img/sponsors/PROSA-logo.png diff --git a/bornhack/static_src/img/sponsors/PS_Logo_CMYK_DK.png b/static_src/img/sponsors/PS_Logo_CMYK_DK.png similarity index 100% rename from bornhack/static_src/img/sponsors/PS_Logo_CMYK_DK.png rename to static_src/img/sponsors/PS_Logo_CMYK_DK.png diff --git a/bornhack/static_src/img/sponsors/ShopGun_LogoType_Green.png b/static_src/img/sponsors/ShopGun_LogoType_Green.png similarity index 100% rename from bornhack/static_src/img/sponsors/ShopGun_LogoType_Green.png rename to static_src/img/sponsors/ShopGun_LogoType_Green.png diff --git a/bornhack/static_src/img/sponsors/bitbureauet.png b/static_src/img/sponsors/bitbureauet.png similarity index 100% rename from bornhack/static_src/img/sponsors/bitbureauet.png rename to static_src/img/sponsors/bitbureauet.png diff --git a/bornhack/static_src/img/sponsors/bornfiber-logoweb.png b/static_src/img/sponsors/bornfiber-logoweb.png similarity index 100% rename from bornhack/static_src/img/sponsors/bornfiber-logoweb.png rename to static_src/img/sponsors/bornfiber-logoweb.png diff --git a/bornhack/static_src/img/sponsors/brklogo.png b/static_src/img/sponsors/brklogo.png similarity index 100% rename from bornhack/static_src/img/sponsors/brklogo.png rename to static_src/img/sponsors/brklogo.png diff --git a/bornhack/static_src/img/sponsors/danskebank.png b/static_src/img/sponsors/danskebank.png similarity index 100% rename from bornhack/static_src/img/sponsors/danskebank.png rename to static_src/img/sponsors/danskebank.png diff --git a/bornhack/static_src/img/sponsors/fortconsultlogo.png b/static_src/img/sponsors/fortconsultlogo.png similarity index 100% rename from bornhack/static_src/img/sponsors/fortconsultlogo.png rename to static_src/img/sponsors/fortconsultlogo.png diff --git a/bornhack/static_src/img/sponsors/prosa-u35-1.png b/static_src/img/sponsors/prosa-u35-1.png similarity index 100% rename from bornhack/static_src/img/sponsors/prosa-u35-1.png rename to static_src/img/sponsors/prosa-u35-1.png diff --git a/bornhack/static_src/img/sponsors/samdatalogo.jpg b/static_src/img/sponsors/samdatalogo.jpg similarity index 100% rename from bornhack/static_src/img/sponsors/samdatalogo.jpg rename to static_src/img/sponsors/samdatalogo.jpg diff --git a/bornhack/static_src/img/sponsors/septima-logo.png b/static_src/img/sponsors/septima-logo.png similarity index 100% rename from bornhack/static_src/img/sponsors/septima-logo.png rename to static_src/img/sponsors/septima-logo.png diff --git a/bornhack/static_src/img/sponsors/tyktech_logo.png b/static_src/img/sponsors/tyktech_logo.png similarity index 100% rename from bornhack/static_src/img/sponsors/tyktech_logo.png rename to static_src/img/sponsors/tyktech_logo.png diff --git a/bornhack/static_src/img/sponsors/zencurity_logo.png b/static_src/img/sponsors/zencurity_logo.png similarity index 100% rename from bornhack/static_src/img/sponsors/zencurity_logo.png rename to static_src/img/sponsors/zencurity_logo.png diff --git a/bornhack/static_src/img/sponsors/zybersafe_logo.png b/static_src/img/sponsors/zybersafe_logo.png similarity index 100% rename from bornhack/static_src/img/sponsors/zybersafe_logo.png rename to static_src/img/sponsors/zybersafe_logo.png diff --git a/bornhack/static_src/js/bootstrap.js b/static_src/js/bootstrap.js similarity index 100% rename from bornhack/static_src/js/bootstrap.js rename to static_src/js/bootstrap.js diff --git a/bornhack/static_src/js/bootstrap.min.js b/static_src/js/bootstrap.min.js similarity index 100% rename from bornhack/static_src/js/bootstrap.min.js rename to static_src/js/bootstrap.min.js diff --git a/bornhack/static_src/js/images/layers-2x.png b/static_src/js/images/layers-2x.png similarity index 100% rename from bornhack/static_src/js/images/layers-2x.png rename to static_src/js/images/layers-2x.png diff --git a/bornhack/static_src/js/images/layers.png b/static_src/js/images/layers.png similarity index 100% rename from bornhack/static_src/js/images/layers.png rename to static_src/js/images/layers.png diff --git a/bornhack/static_src/js/images/marker-icon-2x.png b/static_src/js/images/marker-icon-2x.png similarity index 100% rename from bornhack/static_src/js/images/marker-icon-2x.png rename to static_src/js/images/marker-icon-2x.png diff --git a/bornhack/static_src/js/images/marker-icon.png b/static_src/js/images/marker-icon.png similarity index 100% rename from bornhack/static_src/js/images/marker-icon.png rename to static_src/js/images/marker-icon.png diff --git a/bornhack/static_src/js/images/marker-shadow.png b/static_src/js/images/marker-shadow.png similarity index 100% rename from bornhack/static_src/js/images/marker-shadow.png rename to static_src/js/images/marker-shadow.png diff --git a/bornhack/static_src/js/jquery.min.js b/static_src/js/jquery.min.js similarity index 100% rename from bornhack/static_src/js/jquery.min.js rename to static_src/js/jquery.min.js diff --git a/bornhack/static_src/js/leaflet-src.js b/static_src/js/leaflet-src.js similarity index 100% rename from bornhack/static_src/js/leaflet-src.js rename to static_src/js/leaflet-src.js diff --git a/bornhack/static_src/js/leaflet.js b/static_src/js/leaflet.js similarity index 100% rename from bornhack/static_src/js/leaflet.js rename to static_src/js/leaflet.js diff --git a/bornhack/static_src/pdf/bornhack_2016_letterhead.odt b/static_src/pdf/bornhack_2016_letterhead.odt similarity index 100% rename from bornhack/static_src/pdf/bornhack_2016_letterhead.odt rename to static_src/pdf/bornhack_2016_letterhead.odt diff --git a/bornhack/static_src/pdf/bornhack_2016_letterhead.pdf b/static_src/pdf/bornhack_2016_letterhead.pdf similarity index 100% rename from bornhack/static_src/pdf/bornhack_2016_letterhead.pdf rename to static_src/pdf/bornhack_2016_letterhead.pdf diff --git a/bornhack/static_src/pdf/bornhack_2016_test_letterhead.odt b/static_src/pdf/bornhack_2016_test_letterhead.odt similarity index 100% rename from bornhack/static_src/pdf/bornhack_2016_test_letterhead.odt rename to static_src/pdf/bornhack_2016_test_letterhead.odt diff --git a/bornhack/static_src/pdf/bornhack_2016_test_letterhead.pdf b/static_src/pdf/bornhack_2016_test_letterhead.pdf similarity index 100% rename from bornhack/static_src/pdf/bornhack_2016_test_letterhead.pdf rename to static_src/pdf/bornhack_2016_test_letterhead.pdf diff --git a/bornhack/static_src/pdf/bornhack_webshop_cancellation_form.pdf b/static_src/pdf/bornhack_webshop_cancellation_form.pdf similarity index 100% rename from bornhack/static_src/pdf/bornhack_webshop_cancellation_form.pdf rename to static_src/pdf/bornhack_webshop_cancellation_form.pdf diff --git a/bornhack/templates/info.html b/templates/2016info.html similarity index 100% rename from bornhack/templates/info.html rename to templates/2016info.html diff --git a/bornhack/templates/404.html b/templates/404.html similarity index 100% rename from bornhack/templates/404.html rename to templates/404.html diff --git a/bornhack/templates/500.html b/templates/500.html similarity index 100% rename from bornhack/templates/500.html rename to templates/500.html diff --git a/bornhack/templates/base.html b/templates/base.html similarity index 56% rename from bornhack/templates/base.html rename to templates/base.html index 675664fd..d0bb073c 100644 --- a/bornhack/templates/base.html +++ b/templates/base.html @@ -1,5 +1,8 @@ {% load static from staticfiles %} {% load bootstrap3 %} +{% load menubutton %} +{% static "" as baseurl %} + @@ -15,12 +18,13 @@ + {% bootstrap_javascript jquery=1 %} + {% block extra_head %} {% endblock %} -

+ {% if camp %} + + {% endif %} {% bootstrap_messages %} {% block content %}{% endblock %}
- {% bootstrap_javascript jquery=1 %} + diff --git a/bornhack/templates/coc.html b/templates/coc.html similarity index 100% rename from bornhack/templates/coc.html rename to templates/coc.html diff --git a/bornhack/templates/contact.html b/templates/contact.html similarity index 100% rename from bornhack/templates/contact.html rename to templates/contact.html diff --git a/templates/frontpage.html b/templates/frontpage.html new file mode 100644 index 00000000..00fa0c4b --- /dev/null +++ b/templates/frontpage.html @@ -0,0 +1,91 @@ +{% extends 'base.html' %} +{% load static from staticfiles %} +{% load imageutils %} +{% block content %} + +
+
+ + +
+ +
+
+
+ Bornhack is an annual 7 day outdoor tent camping festival + where people with an interest in technology, security and society + come together to celebrate technology, socialise, learn and have fun. +
+
+
+ {% thumbnail 'img/bornhack-2016/fonsmark' 'FA0_1961.JPG' 'BornHack' %} +
+
+ +
+ +
+
+ {% thumbnail 'img/bornhack-2016/ahf' 'jarlsgaard.jpg' 'JarlsgÄrd buildings from above' %} +
+
+
+ The first Bornhack took place from August 27 to September 3rd 2016 + on the Danish island of Bornholm. We are preparing + BornHack 2017 which will be from 22nd to 27th of August 2017. +
+
+
+ +
+ +
+
+

+ Bornhack is a participatory event where we expect people + to come up with ideas and projects, and help make the content. We will reach out to + people to ensure interesting, high quality talks and workshops. +

+
+
+ {% thumbnail 'img/bornhack-2016/fonsmark' 'FB1_5090.JPG' 'Talk at BornHack 2016' %} +
+
+ +
+ +

+ You are very welcome to ask questions and show your interest on our different channels: +

+ {% include 'includes/contact.html' %} +

+ Follow and like us to get updates about BornHack. +

+ +

+ We hope you want to be a part of Bornhack. Ticket sales for 2017 will be open soon! +

+ +
+

+ Sincerely
+ The Bornhack Team +

+
+ +

+ {% thumbnail 'img/bornhack-2016/fonsmark' 'FA0_1983.JPG' 'Happy organisers welcoming people at the entrance to BornHack 2016' %} + {% thumbnail 'img/bornhack-2016/fonsmark' 'FA0_1986.JPG' 'A bus full of hackers arrive at BornHack 2016' %} + {% thumbnail 'img/bornhack-2016/fonsmark' 'FB1_5126.JPG' 'Late night hacking at Baconsvin village at BornHack 2016' %} + {% thumbnail 'img/bornhack-2016/fonsmark' 'FB1_5168.JPG' '#irl_bar by night at BornHack 2016' %} + {% thumbnail 'img/bornhack-2016/fonsmark' 'FB1_5265.JPG' 'Happy organisers welcoming people to BornHack 2016' %} + {% thumbnail 'img/bornhack-2016/esbjerg' '1600x900-B12A2452.jpg' 'Soldering the BornHack 2016 badge' %} + {% thumbnail 'img/bornhack-2016/esbjerg' '1600x900-B12A2485.jpg' 'Colored light in the grass' %} + {% thumbnail 'img/bornhack-2016/esbjerg' '1600x988-B12A2624.jpg' 'Working on decorations' %} + {% thumbnail 'img/bornhack-2016/esbjerg' '1600x900-B12A2604.jpg' 'Sitting around the campfire at BornHack 2016' %} +

+
+ + +{% endblock %} + diff --git a/bornhack/templates/includes/contact.html b/templates/includes/contact.html similarity index 100% rename from bornhack/templates/includes/contact.html rename to templates/includes/contact.html diff --git a/bornhack/templates/legal/general_terms_and_conditions.html b/templates/legal/general_terms_and_conditions.html similarity index 100% rename from bornhack/templates/legal/general_terms_and_conditions.html rename to templates/legal/general_terms_and_conditions.html diff --git a/bornhack/templates/legal/privacy_policy.html b/templates/legal/privacy_policy.html similarity index 100% rename from bornhack/templates/legal/privacy_policy.html rename to templates/legal/privacy_policy.html diff --git a/utils/management/__init__.py b/utils/management/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/utils/management/commands/__init__.py b/utils/management/commands/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/utils/management/commands/bootstrap-devsite.py b/utils/management/commands/bootstrap-devsite.py new file mode 100644 index 00000000..4a92b302 --- /dev/null +++ b/utils/management/commands/bootstrap-devsite.py @@ -0,0 +1,335 @@ +from django.core.management.base import BaseCommand +from django.conf import settings +from django.utils import timezone +from camps.models import Camp +from news.models import NewsItem +from program.models import EventType, Event, EventInstance, Speaker +import datetime +from django.contrib.auth.models import User + + +class Command(BaseCommand): + args = 'none' + help = 'Create mock data for development instances' + + def output(self, message): + self.stdout.write('%s: %s' % (timezone.now().strftime("%Y-%m-%d %H:%M:%S"), message)) + + def handle(self, *args, **options): + self.output('Creating camps...') + camp1 = Camp.objects.create( + title='BornHack 2016', + tagline='Initial Commit', + slug='bornhack-2016', + buildup = ( + timezone.datetime(2016, 8, 25, 12, 0, tzinfo=timezone.utc), + timezone.datetime(2016, 8, 27, 12, 0, tzinfo=timezone.utc), + ), + camp = ( + timezone.datetime(2016, 8, 27, 12, 0, tzinfo=timezone.utc), + timezone.datetime(2016, 9, 04, 12, 0, tzinfo=timezone.utc), + ), + teardown = ( + timezone.datetime(2016, 9, 04, 12, 0, tzinfo=timezone.utc), + timezone.datetime(2016, 9, 06, 12, 0, tzinfo=timezone.utc), + ), + ) + + camp2 = Camp.objects.create( + title='BornHack 2017', + tagline='Make Tradition', + slug='bornhack-2017', + buildup = ( + timezone.datetime(2017, 8, 20, 12, 0, tzinfo=timezone.utc), + timezone.datetime(2017, 8, 22, 12, 0, tzinfo=timezone.utc), + ), + camp = ( + timezone.datetime(2017, 8, 22, 12, 0, tzinfo=timezone.utc), + timezone.datetime(2017, 8, 29, 12, 0, tzinfo=timezone.utc), + ), + teardown = ( + timezone.datetime(2015, 8, 29, 12, 0, tzinfo=timezone.utc), + timezone.datetime(2015, 8, 31, 12, 0, tzinfo=timezone.utc), + ), + ) + + camp3 = Camp.objects.create( + title='BornHack 2018', + tagline='Undecided', + slug='bornhack-2018', + buildup = ( + timezone.datetime(2018, 8, 13, 12, 0, tzinfo=timezone.utc), + timezone.datetime(2018, 8, 16, 12, 0, tzinfo=timezone.utc), + ), + camp = ( + timezone.datetime(2018, 8, 16, 12, 0, tzinfo=timezone.utc), + timezone.datetime(2018, 8, 23, 12, 0, tzinfo=timezone.utc), + ), + teardown = ( + timezone.datetime(2018, 8, 23, 12, 0, tzinfo=timezone.utc), + timezone.datetime(2018, 8, 26, 12, 0, tzinfo=timezone.utc), + ), + ) + + self.output('Creating news...') + news1 = NewsItem.objects.create( + title='welcome to bornhack 2016', + content='news body here with html support', + published_at=timezone.datetime(2016, 8, 27, 12, 0, tzinfo=timezone.utc) + ) + news2 = NewsItem.objects.create( + title='bornhack 2016 is over', + content='news body here', + published_at=timezone.datetime(2016, 9, 4, 12, 0, tzinfo=timezone.utc) + ) + news3 = NewsItem.objects.create( + title='unpublished news item', + content='unpublished news body here', + ) + news4 = NewsItem.objects.create( + title='welcome to bornhack 2017', + content='news body here', + published_at=timezone.datetime(2017, 8, 22, 12, 0, tzinfo=timezone.utc), + archived=True + ) + news5 = NewsItem.objects.create( + title='bornhack 2017 is over', + content='news body here', + published_at=timezone.datetime(2017, 8, 29, 12, 0, tzinfo=timezone.utc), + archived=True + ) + + self.output("creating users...") + user1 = User.objects.create_user( + username='user1', + email='user1@example.com', + password='user1', + ) + user2 = User.objects.create_user( + username='user2', + email='user2@example.com', + password='user2', + ) + user3 = User.objects.create_user( + username='user3', + email='user3@example.com', + password='user3', + ) + user4 = User.objects.create_user( + username='user4', + email='user4@example.com', + password='user4', + ) + admin = User.objects.create_superuser( + username='admin', + email='admin@example.com', + password='admin', + ) + + self.output("creating event types...") + et1 = EventType.objects.create( + name='Workshops', + slug='workshops', + color='#ff9900', + light_text=False + ) + + et2 = EventType.objects.create( + name='Talks', + slug='talks', + color='#2D9595', + light_text=True + ) + + et3 = EventType.objects.create( + name='Keynotes', + slug='keynotes', + color='#FF3453', + light_text=True + ) + + et4 = EventType.objects.create( + name='Facilities', + slug='facilities', + color='#cccccc', + light_text=False + ) + + + self.output("creating events...") + ev1 = Event.objects.create( + title='Developing the BornHack website', + abstract='abstract here, bla bla bla', + event_type=et2, + camp=camp1 + ) + ev2 = Event.objects.create( + title='State of the world', + abstract='abstract here, bla bla bla', + event_type=et3, + camp=camp1 + ) + ev3 = Event.objects.create( + title='Welcome to bornhack!', + abstract='abstract here, bla bla bla', + event_type=et2, + camp=camp1 + ) + ev4 = Event.objects.create( + title='bar is open', + abstract='the bar is open, yay', + event_type=et4, + camp=camp1 + ) + + ev5 = Event.objects.create( + title='Network something', + abstract='abstract here, bla bla bla', + event_type=et2, + camp=camp2 + ) + ev6 = Event.objects.create( + title='State of outer space', + abstract='abstract here, bla bla bla', + event_type=et3, + camp=camp2 + ) + ev7 = Event.objects.create( + title='Welcome to bornhack!', + abstract='abstract here, bla bla bla', + event_type=et2, + camp=camp2 + ) + ev8 = Event.objects.create( + title='bar is open', + abstract='the bar is open, yay', + event_type=et4, + camp=camp2 + ) + + + self.output("creating speakers...") + sp1 = Speaker.objects.create( + name='Henrik Kramse', + biography='Henrik is an internet samurai working in internet and security around the world.', + slug='henrik-kramshj' + ) + sp1.events.add(ev5, ev6) + sp2 = Speaker.objects.create( + name='Thomas Tykling', + biography='random danish hacker', + slug='thomas-tykling' + ) + sp2.events.add(ev7, ev3, ev1) + sp3 = Speaker.objects.create( + name='Alex Ahf', + biography='functional alcoholic', + slug='alex-ahf' + ) + sp3.events.add(ev4, ev8, ev2) + + self.output("creating eventinstances...") + ei1 = EventInstance.objects.create( + event=ev3, + when=( + timezone.datetime(2016, 8, 27, 12, 0, tzinfo=timezone.utc), + timezone.datetime(2016, 8, 27, 13, 0, tzinfo=timezone.utc) + ) + ) + ei2 = EventInstance.objects.create( + event=ev1, + when=( + timezone.datetime(2016, 8, 28, 12, 0, tzinfo=timezone.utc), + timezone.datetime(2016, 8, 28, 13, 0, tzinfo=timezone.utc), + ) + ) + ei3 = EventInstance.objects.create( + event=ev2, + when=( + timezone.datetime(2016, 8, 29, 12, 0, tzinfo=timezone.utc), + timezone.datetime(2016, 8, 29, 13, 0, tzinfo=timezone.utc), + ) + ) + ei4 = EventInstance.objects.create( + event=ev4, + when=( + timezone.datetime(2016, 8, 27, 12, 0, tzinfo=timezone.utc), + timezone.datetime(2016, 8, 28, 5, 0, tzinfo=timezone.utc), + ) + ) + ei5 = EventInstance.objects.create( + event=ev4, + when=( + timezone.datetime(2016, 8, 28, 12, 0, tzinfo=timezone.utc), + timezone.datetime(2016, 8, 29, 5, 0, tzinfo=timezone.utc), + ) + ) + ei6 = EventInstance.objects.create( + event=ev4, + when=( + timezone.datetime(2016, 8, 29, 12, 0, tzinfo=timezone.utc), + timezone.datetime(2016, 8, 30, 5, 0, tzinfo=timezone.utc), + ) + ) + ei7 = EventInstance.objects.create( + event=ev4, + when=( + timezone.datetime(2016, 8, 30, 12, 0, tzinfo=timezone.utc), + timezone.datetime(2016, 8, 31, 5, 0, tzinfo=timezone.utc), + ) + ) + + ei8 = EventInstance.objects.create( + event=ev7, + when=( + timezone.datetime(2016, 8, 27, 12, 0, tzinfo=timezone.utc), + timezone.datetime(2016, 8, 27, 13, 0, tzinfo=timezone.utc), + ) + ) + ei9 = EventInstance.objects.create( + event=ev5, + when=( + timezone.datetime(2016, 8, 28, 12, 0, tzinfo=timezone.utc), + timezone.datetime(2016, 8, 28, 13, 0, tzinfo=timezone.utc), + ) + ) + ei10 = EventInstance.objects.create( + event=ev6, + when=( + timezone.datetime(2016, 8, 29, 12, 0, tzinfo=timezone.utc), + timezone.datetime(2016, 8, 29, 13, 0, tzinfo=timezone.utc), + ) + ) + ei11 = EventInstance.objects.create( + event=ev8, + when=( + timezone.datetime(2016, 8, 27, 12, 0, tzinfo=timezone.utc), + timezone.datetime(2016, 8, 28, 5, 0, tzinfo=timezone.utc), + ) + ) + ei12 = EventInstance.objects.create( + event=ev8, + when=( + timezone.datetime(2016, 8, 28, 12, 0, tzinfo=timezone.utc), + timezone.datetime(2016, 8, 29, 5, 0, tzinfo=timezone.utc), + ) + ) + ei13 = EventInstance.objects.create( + event=ev8, + when=( + timezone.datetime(2016, 8, 29, 12, 0, tzinfo=timezone.utc), + timezone.datetime(2016, 8, 30, 5, 0, tzinfo=timezone.utc), + ) + ) + ei14 = EventInstance.objects.create( + event=ev8, + when=( + timezone.datetime(2016, 8, 30, 12, 0, tzinfo=timezone.utc), + timezone.datetime(2016, 8, 31, 5, 0, tzinfo=timezone.utc), + ) + ) + + + self.output("created users user1/user1 user2/user2 user3/user3 user4/user4 and admin user admin/admin") + self.output("done!") + diff --git a/utils/models.py b/utils/models.py index 8dc5292c..54da5790 100644 --- a/utils/models.py +++ b/utils/models.py @@ -1,9 +1,28 @@ import uuid - +from django.core.exceptions import ValidationError from django.db import models -class UUIDModel(models.Model): +class CleanedModel(models.Model): + class Meta: + abstract = True + + def save(self, **kwargs): + try: + # call this models full_clean() method before saving, + # which in turn calls .clean_fields(), .clean() and .validate_unique() + self.full_clean() + except ValidationError as e: + message = "Got ValidationError while saving: %s" % e + if hasattr(self, 'request'): + messages.error(self.request, message) + print(message) + # dont save, just return + return + super(CleanedModel, self).save(**kwargs) + + +class UUIDModel(CleanedModel): class Meta: abstract = True @@ -14,9 +33,11 @@ class UUIDModel(models.Model): ) -class CreatedUpdatedModel(models.Model): +class CreatedUpdatedModel(CleanedModel): class Meta: abstract = True created = models.DateTimeField(auto_now_add=True) - updated = models.DateTimeField(auto_now=True) \ No newline at end of file + updated = models.DateTimeField(auto_now=True) + + diff --git a/utils/templates/thumbnail.html b/utils/templates/thumbnail.html new file mode 100644 index 00000000..2e20a6ac --- /dev/null +++ b/utils/templates/thumbnail.html @@ -0,0 +1,2 @@ +{{ description }} + diff --git a/utils/templatetags/commonmark.py b/utils/templatetags/commonmark.py index d14fba2b..f2410bf4 100644 --- a/utils/templatetags/commonmark.py +++ b/utils/templatetags/commonmark.py @@ -10,6 +10,7 @@ register = template.Library() @register.filter @stringfilter def commonmark(value): + """Returns HTML given some CommonMark Markdown. Does not clean HTML, not for use with untrusted input.""" parser = CommonMark.Parser() renderer = CommonMark.HtmlRenderer() ast = parser.parse(value) @@ -18,6 +19,7 @@ def commonmark(value): @register.filter @stringfilter def unsafecommonmark(value): + """Returns HTML given some CommonMark Markdown. Cleans HTML from input using bleach, suitable for use with untrusted input.""" parser = CommonMark.Parser() renderer = CommonMark.HtmlRenderer() ast = parser.parse(bleach.clean(value)) diff --git a/utils/templatetags/imageutils.py b/utils/templatetags/imageutils.py new file mode 100644 index 00000000..9909a1bc --- /dev/null +++ b/utils/templatetags/imageutils.py @@ -0,0 +1,18 @@ +from django import template +from django.contrib.staticfiles.templatetags.staticfiles import static + +register = template.Library() +@register.inclusion_tag('thumbnail.html') +def thumbnail(path, filename, description): + """ + Returns the HTML to show an image including thumbnail. + Assumes the thumbnail is called 'thumbnail_foo.jpg.png' if the image is called 'foo.jpg'. + Path should be relative inside static root. + Description is used for alt-text and mouseover. + """ + return { + 'path': static('') + path, + 'filename': filename, + 'description': description, + } + diff --git a/utils/templatetags/menubutton.py b/utils/templatetags/menubutton.py new file mode 100644 index 00000000..72fa4986 --- /dev/null +++ b/utils/templatetags/menubutton.py @@ -0,0 +1,12 @@ +import datetime +from django import template + +register = template.Library() + +@register.simple_tag(takes_context=True) +def menubuttonclass(context, appname): + if appname == context['request'].resolver_match.func.view_class.__module__.split(".")[0]: + return "btn-primary" + else: + return "btn-default" + diff --git a/villages/migrations/0006_remove_village_camp.py b/villages/migrations/0006_remove_village_camp.py new file mode 100644 index 00000000..b6c5c25f --- /dev/null +++ b/villages/migrations/0006_remove_village_camp.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.4 on 2016-12-12 17:56 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('villages', '0005_auto_20160712_2036'), + ] + + operations = [ + migrations.RemoveField( + model_name='village', + name='camp', + ), + ] diff --git a/villages/migrations/0007_village_camp.py b/villages/migrations/0007_village_camp.py new file mode 100644 index 00000000..338d8b3c --- /dev/null +++ b/villages/migrations/0007_village_camp.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.4 on 2016-12-28 22:08 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('camps', '0011_auto_20161228_1750'), + ('villages', '0006_remove_village_camp'), + ] + + operations = [ + migrations.AddField( + model_name='village', + name='camp', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='camps.Camp'), + ), + ] diff --git a/villages/migrations/0008_auto_20161228_2209.py b/villages/migrations/0008_auto_20161228_2209.py new file mode 100644 index 00000000..aba16e5c --- /dev/null +++ b/villages/migrations/0008_auto_20161228_2209.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.4 on 2016-12-28 22:09 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('villages', '0007_village_camp'), + ] + + operations = [ + migrations.AlterField( + model_name='village', + name='camp', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='camps.Camp'), + ), + ] diff --git a/villages/migrations/0009_auto_20161229_2143.py b/villages/migrations/0009_auto_20161229_2143.py new file mode 100644 index 00000000..9c85a6de --- /dev/null +++ b/villages/migrations/0009_auto_20161229_2143.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.4 on 2016-12-29 21:43 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('villages', '0008_auto_20161228_2209'), + ] + + operations = [ + migrations.AlterField( + model_name='village', + name='private', + field=models.BooleanField(default=False, help_text='Check if your village is invite only. Leave unchecked to welcome strangers.'), + ), + ] diff --git a/villages/models.py b/villages/models.py index 5104b915..df3ab6db 100644 --- a/villages/models.py +++ b/villages/models.py @@ -4,7 +4,6 @@ from django.core.urlresolvers import reverse_lazy from django.db import models from django.utils.text import slugify -from camps.models import Camp from utils.models import CreatedUpdatedModel, UUIDModel from .managers import VillageQuerySet @@ -15,9 +14,8 @@ class Village(CreatedUpdatedModel, UUIDModel): class Meta: ordering = ['name'] - camp = models.ForeignKey('camps.Camp') contact = models.ForeignKey('auth.User') - + camp = models.ForeignKey('camps.Camp') name = models.CharField(max_length=255) slug = models.SlugField(max_length=255, blank=True) description = models.TextField( @@ -26,7 +24,7 @@ class Village(CreatedUpdatedModel, UUIDModel): private = models.BooleanField( default=False, - help_text='Check if your village is privately organized' + help_text='Check if your village is invite only. Leave unchecked to welcome strangers.' ) deleted = models.BooleanField( @@ -39,7 +37,7 @@ class Village(CreatedUpdatedModel, UUIDModel): return self.name def get_absolute_url(self): - return reverse_lazy('villages:detail', kwargs={'slug': self.slug}) + return reverse_lazy('village_detail', kwargs={'camp_slug': self.camp.slug, 'slug': self.slug}) def save(self, **kwargs): if ( @@ -62,11 +60,9 @@ class Village(CreatedUpdatedModel, UUIDModel): incrementer += 1 self.slug = slug - if not hasattr(self, 'camp'): - self.camp = Camp.objects.current() - super(Village, self).save(**kwargs) def delete(self, using=None, keep_parents=False): self.deleted = True self.save() + diff --git a/villages/templates/village_confirm_delete.html b/villages/templates/village_confirm_delete.html index d4fd816c..f3a8207a 100644 --- a/villages/templates/village_confirm_delete.html +++ b/villages/templates/village_confirm_delete.html @@ -8,6 +8,6 @@

- Cancel + Cancel {% endblock %} diff --git a/villages/templates/village_detail.html b/villages/templates/village_detail.html index db73c6d8..0a49e09e 100644 --- a/villages/templates/village_detail.html +++ b/villages/templates/village_detail.html @@ -13,8 +13,8 @@ Village: {{ village.name }} | {{ block.super }} {% if user == village.contact %}
-Edit -Delete +Edit +Delete {% endif %} {% endblock %} diff --git a/villages/templates/village_form.html b/villages/templates/village_form.html index 3b5f8534..54131317 100644 --- a/villages/templates/village_form.html +++ b/villages/templates/village_form.html @@ -2,7 +2,7 @@ {% load bootstrap3 %} {% block content %} - +

Create {{ camp.title }} Village

{% csrf_token %} @@ -13,4 +13,4 @@
-{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/villages/templates/village_list.html b/villages/templates/village_list.html index 6af03edb..2331e81a 100644 --- a/villages/templates/village_list.html +++ b/villages/templates/village_list.html @@ -6,27 +6,22 @@ Villages | {{ block.super }} {% endblock %} {% block content %} - +

Villages

If this is your first hackercamp the term 'Village' might be confusing but it is fairly simple: a village is just a spot on the campsite where you and a bunch of your friends/likeminded people camp together. Apart from peoples individual tents which they sleep in, many villages bring a large common tent - where you can hack and hang out during the day. -

- -

- - It is also possible to rent a tent, chairs and tables for villages here. - + where you can hack and hang out during the day. It is also possible to rent a + tent, chairs and tables in the shop!

{% if user.is_authenticated %} -Create a village +Create a new {{ camp.title }} village {% endif %}
- +{% if villages %} @@ -39,7 +34,7 @@ Villages | {{ block.super }} {% for village in villages %} @@ -53,5 +48,7 @@ Villages | {{ block.super }} {% endfor %}
- + {{ village.name }}
- -{% endblock %} \ No newline at end of file +{% else %} +

No villages for {{ camp.title }} yet!

+{% endif %} +{% endblock %} diff --git a/villages/views.py b/villages/views.py index 901e79cf..69fa59a9 100644 --- a/villages/views.py +++ b/villages/views.py @@ -2,21 +2,20 @@ from django.http import Http404 from django.contrib.auth.mixins import LoginRequiredMixin from django.core.urlresolvers import reverse_lazy from django.http import HttpResponseRedirect -from django.views.generic import ( - ListView, DetailView, CreateView, UpdateView, DeleteView -) +from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView from django.views.generic.detail import SingleObjectMixin - from .models import Village +from camps.models import Camp +from camps.mixins import CampViewMixin -class VillageListView(ListView): +class VillageListView(CampViewMixin, ListView): queryset = Village.objects.not_deleted() template_name = 'village_list.html' context_object_name = 'villages' -class VillageDetailView(DetailView): +class VillageDetailView(CampViewMixin, DetailView): queryset = Village.objects.not_deleted() template_name = 'village_detail.html' context_object_name = 'village' @@ -31,6 +30,7 @@ class VillageCreateView(LoginRequiredMixin, CreateView): def form_valid(self, form): village = form.save(commit=False) village.contact = self.request.user + village.camp = Camp.objects.get(slug=self.request.session['campslug']) village.save() return HttpResponseRedirect(village.get_absolute_url()) @@ -64,3 +64,4 @@ class VillageDeleteView(EnsureUserOwnsVillageMixin, LoginRequiredMixin, DeleteVi success_url = reverse_lazy('villages:list') template_name = 'village_confirm_delete.html' context_object_name = 'village' +