this is the beginning of multicamp support, much work still to be done
|
@ -42,48 +42,13 @@ INSTALLED_APPS = [
|
||||||
'utils',
|
'utils',
|
||||||
'villages',
|
'villages',
|
||||||
'program',
|
'program',
|
||||||
|
'info',
|
||||||
|
|
||||||
'allauth',
|
'allauth',
|
||||||
'allauth.account',
|
'allauth.account',
|
||||||
'bootstrap3',
|
'bootstrap3',
|
||||||
]
|
]
|
||||||
|
|
||||||
DEBUG = env('DEBUG')
|
|
||||||
if DEBUG:
|
|
||||||
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
|
||||||
INSTALLED_APPS += ['debug_toolbar', ]
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
STATIC_URL = '/static/'
|
STATIC_URL = '/static/'
|
||||||
STATIC_ROOT = local_dir('static')
|
STATIC_ROOT = local_dir('static')
|
||||||
STATICFILES_DIRS = [local_dir('static_src')]
|
STATICFILES_DIRS = [local_dir('static_src')]
|
||||||
|
@ -105,9 +70,9 @@ TEMPLATES = [
|
||||||
'django.template.context_processors.request',
|
'django.template.context_processors.request',
|
||||||
'django.contrib.auth.context_processors.auth',
|
'django.contrib.auth.context_processors.auth',
|
||||||
'django.contrib.messages.context_processors.messages',
|
'django.contrib.messages.context_processors.messages',
|
||||||
'camps.context_processors.current_camp',
|
|
||||||
'shop.context_processors.current_order',
|
'shop.context_processors.current_order',
|
||||||
'shop.context_processors.user_has_tickets',
|
'shop.context_processors.user_has_tickets',
|
||||||
|
'camps.context_processors.camps',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -128,8 +93,8 @@ MIDDLEWARE_CLASSES = [
|
||||||
LOGIN_REDIRECT_URL = 'profiles:detail'
|
LOGIN_REDIRECT_URL = 'profiles:detail'
|
||||||
|
|
||||||
AUTHENTICATION_BACKENDS = (
|
AUTHENTICATION_BACKENDS = (
|
||||||
'django.contrib.auth.backends.ModelBackend', # Login to admin with username
|
'django.contrib.auth.backends.ModelBackend', # Handles login to admin with username
|
||||||
'allauth.account.auth_backends.AuthenticationBackend',
|
'allauth.account.auth_backends.AuthenticationBackend', # Handles regular logins
|
||||||
)
|
)
|
||||||
|
|
||||||
ACCOUNT_AUTHENTICATION_METHOD = 'email'
|
ACCOUNT_AUTHENTICATION_METHOD = 'email'
|
||||||
|
@ -163,3 +128,42 @@ BANKACCOUNT_REG = env('BANKACCOUNT_REG')
|
||||||
BANKACCOUNT_ACCOUNT = env('BANKACCOUNT_ACCOUNT')
|
BANKACCOUNT_ACCOUNT = env('BANKACCOUNT_ACCOUNT')
|
||||||
|
|
||||||
TICKET_CATEGORY_ID = env('TICKET_CATEGORY_ID')
|
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,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,9 @@ from django.conf.urls import include, url
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.views.generic import TemplateView, RedirectView
|
from django.views.generic import TemplateView, RedirectView
|
||||||
from django.core.urlresolvers import reverse_lazy
|
from django.core.urlresolvers import reverse_lazy
|
||||||
|
from camps.views import *
|
||||||
|
from info.views import CampInfoView
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(
|
url(
|
||||||
|
@ -84,4 +87,57 @@ urlpatterns = [
|
||||||
),
|
),
|
||||||
url(r'^accounts/', include('allauth.urls')),
|
url(r'^accounts/', include('allauth.urls')),
|
||||||
url(r'^admin/', include(admin.site.urls)),
|
url(r'^admin/', include(admin.site.urls)),
|
||||||
|
|
||||||
|
# camp specific urls below here
|
||||||
|
|
||||||
|
url(r'(?P<camp_slug>[-_\w+]+)/', include([
|
||||||
|
url(
|
||||||
|
r'^$',
|
||||||
|
CampDetailView.as_view(),
|
||||||
|
name='camp_detail'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
r'^info/$',
|
||||||
|
CampInfoView.as_view(),
|
||||||
|
name='info'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
r'^schedule/$',
|
||||||
|
CampScheduleView.as_view(),
|
||||||
|
name='schedule'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
r'^sponsors/$',
|
||||||
|
CampSponsorView.as_view(),
|
||||||
|
name='camp_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<slug>[-_\w+]+)/delete/$',
|
||||||
|
VillageDeleteView.as_view(),
|
||||||
|
name='village_delete'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
r'(?P<slug>[-_\w+]+)/edit/$',
|
||||||
|
VillageUpdateView.as_view(),
|
||||||
|
name='village_update'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
r'(?P<slug>[-_\w+]+)/$',
|
||||||
|
VillageDetailView.as_view(),
|
||||||
|
name='village_detail'
|
||||||
|
),
|
||||||
|
])),
|
||||||
|
])),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -1,29 +1,8 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
from . import models
|
||||||
from .models import Camp, Day, Expense
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Expense)
|
@admin.register(models.Camp)
|
||||||
class ExpenseAdmin(admin.ModelAdmin):
|
class CampModelAdmin(admin.ModelAdmin):
|
||||||
pass
|
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,
|
|
||||||
]
|
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
|
from django.conf import settings
|
||||||
from .models import Camp
|
from .models import Camp
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
|
||||||
def current_camp(request):
|
def camps(request):
|
||||||
return {'current_camp': Camp.objects.current()}
|
return {
|
||||||
|
'upcoming_camps': Camp.objects.filter(camp_start__gt=timezone.now()),
|
||||||
|
'previous_camps': Camp.objects.filter(camp_start__lt=timezone.now()),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
93
camps/migrations/0007_auto_20161212_1803.py
Normal file
|
@ -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'),
|
||||||
|
),
|
||||||
|
]
|
19
camps/migrations/0008_delete_day.py
Normal file
|
@ -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',
|
||||||
|
),
|
||||||
|
]
|
39
camps/migrations/0009_auto_20161220_1645.py
Normal file
|
@ -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,
|
||||||
|
),
|
||||||
|
]
|
31
camps/migrations/0010_auto_20161220_1714.py
Normal file
|
@ -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,
|
||||||
|
),
|
||||||
|
]
|
13
camps/mixins.py
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
from django.views.generic.detail import SingleObjectMixin
|
||||||
|
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):
|
||||||
|
return self.objects.filter(camp=self.camp)
|
||||||
|
|
142
camps/models.py
|
@ -1,135 +1,93 @@
|
||||||
import datetime
|
import datetime
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from utils.models import UUIDModel, CreatedUpdatedModel
|
from utils.models import UUIDModel, CreatedUpdatedModel
|
||||||
|
|
||||||
from .managers import CampQuerySet
|
|
||||||
|
|
||||||
|
|
||||||
class Camp(CreatedUpdatedModel, UUIDModel):
|
class Camp(CreatedUpdatedModel, UUIDModel):
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _('Camp')
|
verbose_name = 'Camp'
|
||||||
verbose_name_plural = _('Camps')
|
verbose_name_plural = 'Camps'
|
||||||
|
|
||||||
name = models.CharField(
|
title = models.CharField(
|
||||||
verbose_name=_('Name'),
|
verbose_name='Title',
|
||||||
help_text=_('Name of the camp, ie. Bornhack.'),
|
help_text='Title of the camp, ie. Bornhack 2016.',
|
||||||
max_length=255,
|
max_length=255,
|
||||||
)
|
)
|
||||||
|
|
||||||
start = models.DateTimeField(
|
tagline = models.CharField(
|
||||||
verbose_name=_('Start date'),
|
verbose_name='Tagline',
|
||||||
help_text=_('When the camp starts.'),
|
help_text='Tagline of the camp, ie. "Initial Commit"',
|
||||||
unique=True,
|
max_length=255,
|
||||||
)
|
)
|
||||||
|
|
||||||
end = models.DateTimeField(
|
slug = models.SlugField(
|
||||||
verbose_name=_('End date'),
|
verbose_name='Url Slug',
|
||||||
help_text=_('When the camp ends.'),
|
help_text='The url slug to use for this camp'
|
||||||
unique=True,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
shop_open = models.BooleanField(
|
buildup_start = models.DateTimeField(
|
||||||
verbose_name=_('Shop open?'),
|
verbose_name='Buildup Start date',
|
||||||
help_text=_('Whether the shop is open or not.'),
|
help_text='When the camp buildup starts.',
|
||||||
default=False,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
objects = CampQuerySet.as_manager()
|
camp_start = models.DateTimeField(
|
||||||
|
verbose_name='Start date',
|
||||||
def __str__(self):
|
help_text='When the camp starts.',
|
||||||
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(
|
camp_end = models.DateTimeField(
|
||||||
verbose_name=_('Date'),
|
verbose_name='End date',
|
||||||
help_text=_('What date?')
|
help_text='When the camp ends.',
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
teardown_end = models.DateTimeField(
|
||||||
return '{} ({})'.format(
|
verbose_name='Start date',
|
||||||
self.date.strftime('%A'),
|
help_text='When the camp teardown ends.',
|
||||||
self.date
|
)
|
||||||
)
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return "%s - %s" % (self.title, self.tagline)
|
||||||
|
|
||||||
|
|
||||||
class Expense(CreatedUpdatedModel, UUIDModel):
|
class Expense(CreatedUpdatedModel, UUIDModel):
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _('Expense')
|
verbose_name = 'Expense'
|
||||||
verbose_name_plural = _('Expenses')
|
verbose_name_plural = 'Expenses'
|
||||||
|
|
||||||
camp = models.ForeignKey(
|
payment_time = models.DateTimeField(
|
||||||
'camps.Camp',
|
verbose_name='Expense date/time',
|
||||||
verbose_name=_('Camp'),
|
help_text='The date and time this expense was paid.',
|
||||||
help_text=_('The camp to which this expense relates to.'),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
description = models.CharField(
|
description = models.CharField(
|
||||||
verbose_name=_('Description'),
|
verbose_name='Description',
|
||||||
help_text=_('What this expense covers.'),
|
help_text='What this expense covers.',
|
||||||
max_length=255,
|
max_length=255,
|
||||||
)
|
)
|
||||||
|
|
||||||
amount = models.DecimalField(
|
dkk_amount = models.DecimalField(
|
||||||
verbose_name=_('Amount'),
|
verbose_name='DKK Amount',
|
||||||
help_text=_('The amount of the expense.'),
|
help_text='The DKK amount of the expense.',
|
||||||
max_digits=7,
|
max_digits=7,
|
||||||
decimal_places=2,
|
decimal_places=2,
|
||||||
)
|
)
|
||||||
|
|
||||||
CURRENCIES = [
|
receipt = models.ImageField(
|
||||||
('btc', 'BTC'),
|
verbose_name='Image of receipt',
|
||||||
('dkk', 'DKK'),
|
help_text='Upload a scan or image of the receipt',
|
||||||
('eur', 'EUR'),
|
|
||||||
('sek', 'SEK'),
|
|
||||||
]
|
|
||||||
|
|
||||||
currency = models.CharField(
|
|
||||||
verbose_name=_('Currency'),
|
|
||||||
help_text=_('What currency the amount is in.'),
|
|
||||||
choices=CURRENCIES,
|
|
||||||
max_length=3,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
covered_by = models.ForeignKey(
|
refund_user = models.ForeignKey(
|
||||||
'auth.User',
|
'auth.User',
|
||||||
verbose_name=_('Covered by'),
|
verbose_name='Refund user',
|
||||||
help_text=_('Which user, if any, covered this expense.'),
|
help_text='Which user, if any, covered this expense and should be refunded.',
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
refund_paid = models.BooleanField(
|
||||||
return _('{} {} for {}').format(
|
default=False,
|
||||||
self.amount,
|
verbose_name='Refund paid?',
|
||||||
self.get_currency_display(),
|
help_text='Has this expense been refunded to the user?',
|
||||||
self.camp,
|
)
|
||||||
)
|
|
||||||
|
|
5
camps/templates/camp_detail.html
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
{% block content %}
|
||||||
|
{{ camp.title }}
|
||||||
|
{% endblock content %}
|
||||||
|
|
|
@ -1,3 +1,10 @@
|
||||||
from . import models
|
from django.views.generic import ListView, DetailView
|
||||||
|
from django.utils import timezone
|
||||||
|
from .models import *
|
||||||
|
|
||||||
|
|
||||||
|
class CampDetailView(DetailView):
|
||||||
|
model = Camp
|
||||||
|
template_name = 'camp_detail.html'
|
||||||
|
slug_url_kwarg = 'camp_slug'
|
||||||
|
|
||||||
|
|
0
info/__init__.py
Normal file
3
info/admin.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
7
info/apps.py
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class InfoConfig(AppConfig):
|
||||||
|
name = 'info'
|
57
info/migrations/0001_initial.py
Normal file
|
@ -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')]),
|
||||||
|
),
|
||||||
|
]
|
0
info/migrations/__init__.py
Normal file
71
info/models.py
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
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'))
|
||||||
|
|
||||||
|
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.'
|
||||||
|
)
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
if InfoItem.objects.filter(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'})
|
||||||
|
|
||||||
|
|
||||||
|
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.'
|
||||||
|
)
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
if InfoCategory.objects.filter(camp=self.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'})
|
||||||
|
|
||||||
|
|
85
info/templates/info.html
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load static from staticfiles %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
Info | {{ block.super }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_head %}
|
||||||
|
<link rel="stylesheet" href="{% static 'css/leaflet.css' %}" />
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<style>
|
||||||
|
.anchor{
|
||||||
|
display: block;
|
||||||
|
height: 80px; /*same height as header*/
|
||||||
|
margin-top: -80px; /*same height as header*/
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub-anchor{
|
||||||
|
display: block;
|
||||||
|
height: 94px; /*same height as header*/
|
||||||
|
margin-top: -94px; /*same height as header*/
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<h2>Table of Contents</h2>
|
||||||
|
<p class="list-group">
|
||||||
|
{% for category in categories %}
|
||||||
|
<a href="#{{ category.anchor }}" class="list-group-item">{{ category.headline }}</a>
|
||||||
|
{% endfor %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% for category in categories %}
|
||||||
|
<span class="anchor" id="{{ category.anchor }}"></span>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<h2>When is BornHack happening?</h2>
|
||||||
|
<div class="panel-group">
|
||||||
|
{% for item in category.infoitems %}
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<span class="sub-anchor" id="{{ item.anchor }}"></span>
|
||||||
|
<h4 class="panel-title">
|
||||||
|
{{ item.title }}
|
||||||
|
<a href="#{{ item.anchor }}">
|
||||||
|
<i class="glyphicon glyphicon-link"></i>
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<p>{{ item.body }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<script src="{% static 'js/leaflet.js' %}"></script>
|
||||||
|
<script>
|
||||||
|
var map = L.map('map', {center: [55.131520, 14.903000], zoom: 10});
|
||||||
|
|
||||||
|
L.tileLayer(
|
||||||
|
'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
|
||||||
|
{
|
||||||
|
attribution: '© <a href="http://osm.org/copyright" target="_blank">OpenStreetMap</a> contributors',
|
||||||
|
}
|
||||||
|
).addTo(map);
|
||||||
|
|
||||||
|
var camp_latlong = [55.011520, 14.975360];
|
||||||
|
L.marker(camp_latlong).addTo(map);
|
||||||
|
|
||||||
|
L.marker(camp_latlong).addTo(map)
|
||||||
|
.bindPopup('<strong>Coordinates:</strong><br>55.011520, 14.975360<br><strong>Address:</strong><br>Baunevej 11, 3720 Aakirkeby')
|
||||||
|
.openPopup();
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
17
info/views.py
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
from django.shortcuts import render
|
||||||
|
from django.views.generic import ListView, DetailView
|
||||||
|
from django.utils import timezone
|
||||||
|
from .models import *
|
||||||
|
|
||||||
|
|
||||||
|
class CampInfoView(ListView):
|
||||||
|
model = InfoCategory
|
||||||
|
template_name = 'info.html'
|
||||||
|
context_object_name = 'categories'
|
||||||
|
|
||||||
|
def get_queryset(self, **kwargs):
|
||||||
|
return InfoCategory.objects.filter(
|
||||||
|
camp__slug=self.kwargs['camp_slug']
|
||||||
|
)
|
||||||
|
|
||||||
|
|
|
@ -5,5 +5,15 @@ from . import models
|
||||||
|
|
||||||
@admin.register(models.NewsItem)
|
@admin.register(models.NewsItem)
|
||||||
class NewsItemModelAdmin(admin.ModelAdmin):
|
class NewsItemModelAdmin(admin.ModelAdmin):
|
||||||
list_display = ['title', 'public', 'published_at']
|
list_display = ['title', 'published_at', 'archived']
|
||||||
list_filter = ['public']
|
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'
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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()
|
|
||||||
)
|
|
19
news/migrations/0006_remove_newsitem_public.py
Normal file
|
@ -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',
|
||||||
|
),
|
||||||
|
]
|
20
news/migrations/0007_auto_20161220_1136.py
Normal file
|
@ -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),
|
||||||
|
),
|
||||||
|
]
|
20
news/migrations/0008_newsitem_archived.py
Normal file
|
@ -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),
|
||||||
|
),
|
||||||
|
]
|
|
@ -5,7 +5,6 @@ from django.utils import encoding
|
||||||
from django.utils.text import slugify
|
from django.utils.text import slugify
|
||||||
|
|
||||||
from utils.models import CreatedUpdatedModel
|
from utils.models import CreatedUpdatedModel
|
||||||
from news.managers import NewsItemQuerySet
|
|
||||||
|
|
||||||
|
|
||||||
@encoding.python_2_unicode_compatible
|
@encoding.python_2_unicode_compatible
|
||||||
|
@ -15,36 +14,33 @@ class NewsItem(CreatedUpdatedModel):
|
||||||
|
|
||||||
title = models.CharField(max_length=100)
|
title = models.CharField(max_length=100)
|
||||||
content = models.TextField()
|
content = models.TextField()
|
||||||
public = models.BooleanField(default=False)
|
published_at = models.DateTimeField(null=True, blank=True)
|
||||||
published_at = models.DateTimeField()
|
|
||||||
slug = models.SlugField(max_length=255, blank=True)
|
slug = models.SlugField(max_length=255, blank=True)
|
||||||
|
archived = models.BooleanField(default=False)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.title
|
return self.title
|
||||||
|
|
||||||
def save(self, **kwargs):
|
def save(self, **kwargs):
|
||||||
if (
|
if self.published_at:
|
||||||
not self.pk or
|
# 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
|
||||||
not self.slug or
|
if (not self.pk or not self.slug or NewsItem.objects.filter(slug=self.slug).count() > 1):
|
||||||
NewsItem.objects.filter(slug=self.slug).count() > 1
|
published_at_string = self.published_at.strftime('%Y-%m-%d')
|
||||||
):
|
base_slug = slugify(self.title)
|
||||||
published_at_string = self.published_at.strftime('%Y-%m-%d')
|
slug = '{}-{}'.format(published_at_string, base_slug)
|
||||||
base_slug = slugify(self.title)
|
incrementer = 1
|
||||||
slug = '{}-{}'.format(published_at_string, base_slug)
|
|
||||||
incrementer = 1
|
|
||||||
|
|
||||||
# We have to make sure that the slug won't clash with current slugs
|
# We have to make sure that the slug won't clash with current slugs
|
||||||
while NewsItem.objects.filter(slug=slug).exists():
|
while NewsItem.objects.filter(slug=slug).exists():
|
||||||
if incrementer == 1:
|
if incrementer == 1:
|
||||||
slug = '{}-1'.format(slug)
|
slug = '{}-1'.format(slug)
|
||||||
else:
|
else:
|
||||||
slug = '{}-{}'.format(
|
slug = '{}-{}'.format(
|
||||||
'-'.join(slug.split('-')[:-1]),
|
'-'.join(slug.split('-')[:-1]),
|
||||||
incrementer
|
incrementer
|
||||||
)
|
)
|
||||||
incrementer += 1
|
incrementer += 1
|
||||||
self.slug = slug
|
self.slug = slug
|
||||||
|
|
||||||
super(NewsItem, self).save(**kwargs)
|
super(NewsItem, self).save(**kwargs)
|
||||||
|
|
||||||
objects = NewsItemQuerySet.as_manager()
|
|
||||||
|
|
|
@ -1,33 +1,22 @@
|
||||||
from django.views.generic import ListView, DetailView
|
from django.views.generic import ListView, DetailView
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from . import models
|
from .models import *
|
||||||
|
|
||||||
|
|
||||||
class NewsIndex(ListView):
|
class NewsIndex(ListView):
|
||||||
model = models.NewsItem
|
model = NewsItem
|
||||||
template_name = 'news_index.html'
|
template_name = 'news_index.html'
|
||||||
context_object_name = 'news_items'
|
context_object_name = 'news_items'
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self, **kwargs):
|
||||||
return self.model.objects.public()
|
return NewsItem.objects.filter(
|
||||||
|
published_at__isnull=False,
|
||||||
|
published_at__lt=timezone.now(),
|
||||||
|
archived=False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class NewsDetail(DetailView):
|
class NewsDetail(DetailView):
|
||||||
model = models.NewsItem
|
model = NewsItem
|
||||||
template_name = 'news_detail.html'
|
template_name = 'news_detail.html'
|
||||||
context_object_name = 'news_item'
|
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
|
|
||||||
|
|
||||||
|
|
|
@ -30,3 +30,4 @@ class Profile(CreatedUpdatedModel, UUIDModel):
|
||||||
def create_profile(sender, created, instance, **kwargs):
|
def create_profile(sender, created, instance, **kwargs):
|
||||||
if created:
|
if created:
|
||||||
Profile.objects.create(user=instance)
|
Profile.objects.create(user=instance)
|
||||||
|
|
||||||
|
|
|
@ -22,17 +22,8 @@ class EventAdmin(admin.ModelAdmin):
|
||||||
list_display = [
|
list_display = [
|
||||||
'title',
|
'title',
|
||||||
'event_type',
|
'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 = [
|
inlines = [
|
||||||
SpeakerInline
|
SpeakerInline
|
||||||
]
|
]
|
||||||
|
|
52
program/migrations/0010_auto_20161212_1809.py
Normal file
|
@ -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'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -23,9 +23,7 @@ class Event(CreatedUpdatedModel):
|
||||||
slug = models.SlugField(blank=True, max_length=255)
|
slug = models.SlugField(blank=True, max_length=255)
|
||||||
abstract = models.TextField()
|
abstract = models.TextField()
|
||||||
event_type = models.ForeignKey(EventType)
|
event_type = models.ForeignKey(EventType)
|
||||||
days = models.ManyToManyField('camps.Day', blank=True)
|
camp = models.ForeignKey('camps.Camp', null=True)
|
||||||
start = models.TimeField(null=True, blank=True)
|
|
||||||
end = models.TimeField(null=True, blank=True)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['title']
|
ordering = ['title']
|
||||||
|
@ -39,6 +37,20 @@ class Event(CreatedUpdatedModel):
|
||||||
super(Event, self).save(**kwargs)
|
super(Event, self).save(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class EventInstance(CreatedUpdatedModel):
|
||||||
|
""" An instance of an event """
|
||||||
|
event = models.ForeignKey('program.event', related_name='instances')
|
||||||
|
start = models.DateTimeField()
|
||||||
|
end = models.DateTimeField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ['start']
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return '%s (%s to %s)' % (self.event, self.start, self.end)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Speaker(CreatedUpdatedModel):
|
class Speaker(CreatedUpdatedModel):
|
||||||
""" Person anchoring an event. """
|
""" Person anchoring an event. """
|
||||||
name = models.CharField(max_length=150)
|
name = models.CharField(max_length=150)
|
||||||
|
|
|
@ -2,24 +2,27 @@ from collections import OrderedDict
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
from django.views.generic import ListView, TemplateView, DetailView
|
from django.views.generic import ListView, TemplateView, DetailView
|
||||||
|
from camp.mixins import CampViewMixin
|
||||||
|
|
||||||
from camps.models import Day
|
|
||||||
from . import models
|
from . import models
|
||||||
|
|
||||||
|
|
||||||
class SpeakerDetailView(DetailView):
|
class SpeakerDetailView(CampViewMixin, DetailView):
|
||||||
model = models.Speaker
|
model = models.Speaker
|
||||||
template_name = 'speaker_detail.html'
|
template_name = 'speaker_detail.html'
|
||||||
|
|
||||||
class SpeakerListView(ListView):
|
|
||||||
|
class SpeakerListView(CampViewMixin, ListView):
|
||||||
model = models.Speaker
|
model = models.Speaker
|
||||||
template_name = 'speaker_list.html'
|
template_name = 'speaker_list.html'
|
||||||
|
|
||||||
class EventListView(ListView):
|
|
||||||
|
class EventListView(CampViewMixin, ListView):
|
||||||
model = models.Event
|
model = models.Event
|
||||||
template_name = 'event_list.html'
|
template_name = 'event_list.html'
|
||||||
|
|
||||||
class ProgramOverviewView(ListView):
|
|
||||||
|
class ProgramOverviewView(CampViewMixin, ListView):
|
||||||
model = models.Event
|
model = models.Event
|
||||||
template_name = 'program_overview.html'
|
template_name = 'program_overview.html'
|
||||||
|
|
||||||
|
@ -54,7 +57,7 @@ class ProgramOverviewView(ListView):
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
class ProgramDayView(TemplateView):
|
class ProgramDayView(CampViewMixin, TemplateView):
|
||||||
template_name = 'program_day.html'
|
template_name = 'program_day.html'
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
|
@ -71,7 +74,7 @@ class ProgramDayView(TemplateView):
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
class EventDetailView(DetailView):
|
class EventDetailView(CampViewMixin, DetailView):
|
||||||
model = models.Event
|
model = models.Event
|
||||||
template_name = 'program_event_detail.html'
|
template_name = 'program_event_detail.html'
|
||||||
|
|
||||||
|
@ -80,3 +83,5 @@ class EventDetailView(DetailView):
|
||||||
# TODO: date__year is hardcoded here - need fix for 2017 :P
|
# TODO: date__year is hardcoded here - need fix for 2017 :P
|
||||||
context['days'] = Day.objects.filter(date__year=2016)
|
context['days'] = Day.objects.filter(date__year=2016)
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,27 @@
|
||||||
django>=1.10
|
|
||||||
django-allauth>=0.29.0
|
|
||||||
django-bootstrap3>=7.0.1
|
|
||||||
django-environ>=0.4.1
|
|
||||||
psycopg2>=2.6.2
|
|
||||||
PyPDF2>=1.26
|
|
||||||
django-wkhtmltopdf>=3.1.0
|
|
||||||
Pillow==3.2.0
|
|
||||||
qrcode==5.3
|
|
||||||
CommonMark==0.7.2
|
CommonMark==0.7.2
|
||||||
|
Django==1.10.4
|
||||||
|
Pillow==3.2.0
|
||||||
|
PyPDF2==1.26.0
|
||||||
|
Unidecode==0.04.19
|
||||||
|
argparse==1.2.1
|
||||||
|
bleach==1.5.0
|
||||||
|
django-allauth==0.29.0
|
||||||
django-bleach==0.3.0
|
django-bleach==0.3.0
|
||||||
Unidecode==0.4.19
|
django-bootstrap3==7.1.0
|
||||||
django-debug-toolbar>=1.6
|
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
|
||||||
|
psycopg2==2.6.2
|
||||||
|
python-openid==2.2.5
|
||||||
|
qrcode==5.3
|
||||||
|
requests==2.12.3
|
||||||
|
requests-oauthlib==0.7.0
|
||||||
|
six==1.10.0
|
||||||
|
sqlparse==0.2.2
|
||||||
|
webencodings==0.5
|
||||||
|
wsgiref==0.1.2
|
||||||
|
pytz==2016.10
|
||||||
|
|
||||||
|
|
|
@ -58,7 +58,6 @@ class OrderAdmin(admin.ModelAdmin):
|
||||||
]
|
]
|
||||||
|
|
||||||
list_filter = [
|
list_filter = [
|
||||||
'camp',
|
|
||||||
'payment_method',
|
'payment_method',
|
||||||
'open',
|
'open',
|
||||||
'paid',
|
'paid',
|
||||||
|
|
|
@ -20,3 +20,4 @@ def user_has_tickets(request):
|
||||||
).exists():
|
).exists():
|
||||||
has_tickets = True
|
has_tickets = True
|
||||||
return {'has_tickets': has_tickets}
|
return {'has_tickets': has_tickets}
|
||||||
|
|
||||||
|
|
23
shop/migrations/0033_auto_20161212_1756.py
Normal file
|
@ -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',
|
||||||
|
),
|
||||||
|
]
|
|
@ -17,12 +17,6 @@ from unidecode import unidecode
|
||||||
|
|
||||||
|
|
||||||
class CustomOrder(CreatedUpdatedModel):
|
class CustomOrder(CreatedUpdatedModel):
|
||||||
camp = models.ForeignKey(
|
|
||||||
'camps.Camp',
|
|
||||||
verbose_name=_('Camp'),
|
|
||||||
help_text=_('The camp this custom order is for.'),
|
|
||||||
)
|
|
||||||
|
|
||||||
text = models.TextField(
|
text = models.TextField(
|
||||||
help_text=_('The invoice text')
|
help_text=_('The invoice text')
|
||||||
)
|
)
|
||||||
|
@ -78,12 +72,6 @@ class Order(CreatedUpdatedModel):
|
||||||
default=True,
|
default=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
camp = models.ForeignKey(
|
|
||||||
'camps.Camp',
|
|
||||||
verbose_name=_('Camp'),
|
|
||||||
help_text=_('The camp this shop order is for.'),
|
|
||||||
)
|
|
||||||
|
|
||||||
CREDIT_CARD = 'credit_card'
|
CREDIT_CARD = 'credit_card'
|
||||||
BLOCKCHAIN = 'blockchain'
|
BLOCKCHAIN = 'blockchain'
|
||||||
BANK_TRANSFER = 'bank_transfer'
|
BANK_TRANSFER = 'bank_transfer'
|
||||||
|
@ -168,7 +156,7 @@ class Order(CreatedUpdatedModel):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def description(self):
|
def description(self):
|
||||||
return "BornHack %s order #%s" % (self.camp.start.year, self.pk)
|
return "Order #%s" % self.pk
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return str(reverse_lazy('shop:order_detail', kwargs={'pk': self.pk}))
|
return str(reverse_lazy('shop:order_detail', kwargs={'pk': self.pk}))
|
||||||
|
|
|
@ -20,7 +20,6 @@ from django.views.decorators.csrf import csrf_exempt
|
||||||
from django.utils.dateparse import parse_datetime
|
from django.utils.dateparse import parse_datetime
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from camps.models import Camp
|
|
||||||
from shop.models import (
|
from shop.models import (
|
||||||
Order,
|
Order,
|
||||||
Product,
|
Product,
|
||||||
|
@ -234,7 +233,6 @@ class ProductDetailView(FormView, DetailView):
|
||||||
# no open order - open a new one
|
# no open order - open a new one
|
||||||
order = Order.objects.create(
|
order = Order.objects.create(
|
||||||
user=self.request.user,
|
user=self.request.user,
|
||||||
camp=Camp.objects.current()
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# get product from kwargs
|
# get product from kwargs
|
||||||
|
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 5.8 KiB |
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 9.7 KiB After Width: | Height: | Size: 9.7 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 8.7 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 4 KiB After Width: | Height: | Size: 4 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.9 KiB |
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 8.7 KiB |
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 8.6 KiB |
Before Width: | Height: | Size: 2 KiB After Width: | Height: | Size: 2 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 797 B After Width: | Height: | Size: 797 B |
|
@ -38,14 +38,26 @@
|
||||||
<div id="navbar" class="navbar-collapse collapse">
|
<div id="navbar" class="navbar-collapse collapse">
|
||||||
<ul class="nav navbar-nav">
|
<ul class="nav navbar-nav">
|
||||||
<li><a href="{% url 'news:index' %}">News</a></li>
|
<li><a href="{% url 'news:index' %}">News</a></li>
|
||||||
<li><a href="{% url 'info' %}">Info</a></li>
|
|
||||||
|
|
||||||
{% if current_camp.shop_open %}
|
|
||||||
<li><a href="{% url 'shop:index' %}">Shop</a></li>
|
<li><a href="{% url 'shop:index' %}">Shop</a></li>
|
||||||
|
<li class="dropdown">
|
||||||
|
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Camps <span class="caret"></span></a>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
{% for camp in upcoming_camps %}
|
||||||
|
<li><a href="{% url 'camp_detail' camp_slug=camp.slug %}">{{ camp.title }}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
<li role="separator" class="divider"></li>
|
||||||
|
{% for camp in previous_camps %}
|
||||||
|
<li><a href="{% url 'camp_detail' camp_slug=camp.slug %}">{{ camp.title }}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
{# only include camp specific menu items when relevant #}
|
||||||
|
{% if request.resolver_match.kwargs.camp_slug %}
|
||||||
|
<li><a href="{% url 'camp_info' camp_slug=camp.slug %}">Info</a></li>
|
||||||
|
<li><a href="{% url 'camp_villages' camp_slug=camp.slug %}">Villages</a></li>
|
||||||
|
<li><a href="{% url 'camp_schedule' camp_slug=camp.slug %}">Schedule</a></li>
|
||||||
|
<li><a href="{% url 'camp_sponsors' camp_slug=camp.slug %}">Sponsors</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<li><a href="{% url 'villages:list' %}">Villages</a></li>
|
|
||||||
<li><a href="{% url 'schedule:index' %}">Schedule</a></li>
|
|
||||||
<li><a href="{% url 'call-for-sponsors' %}">Sponsors</a></li>
|
|
||||||
<li><a href="{% url 'contact' %}">Contact</a></li>
|
<li><a href="{% url 'contact' %}">Contact</a></li>
|
||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated %}
|
||||||
<li><a href="{% url 'profiles:detail' %}">Profile</a></li>
|
<li><a href="{% url 'profiles:detail' %}">Profile</a></li>
|
||||||
|
@ -55,7 +67,6 @@
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div id="main" class="container container-fluid">
|
<div id="main" class="container container-fluid">
|