this is the beginning of multicamp support, much work still to be done

This commit is contained in:
Thomas Steen Rasmussen 2016-12-25 15:52:55 +01:00
parent d54bd2a84f
commit f8a513ec72
109 changed files with 1167 additions and 286 deletions

View file

@ -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,
},
}
}

View file

@ -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'
),
])),
])),
] ]

View file

@ -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,
]

View file

@ -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()),
}

View file

@ -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

View 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'),
),
]

View 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',
),
]

View 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,
),
]

View 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
View 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)

View file

@ -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, )
)

View file

@ -0,0 +1,5 @@
{% extends 'base.html' %}
{% block content %}
{{ camp.title }}
{% endblock content %}

View file

@ -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
View file

3
info/admin.py Normal file
View file

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

7
info/apps.py Normal file
View file

@ -0,0 +1,7 @@
from __future__ import unicode_literals
from django.apps import AppConfig
class InfoConfig(AppConfig):
name = 'info'

View 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')]),
),
]

View file

71
info/models.py Normal file
View 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
View 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: '&copy; <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
View 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']
)

View file

@ -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'

View file

@ -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()
)

View 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',
),
]

View 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),
),
]

View 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),
),
]

View file

@ -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()

View file

@ -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

View file

@ -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)

View file

@ -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
] ]

View 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'),
),
]

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -58,7 +58,6 @@ class OrderAdmin(admin.ModelAdmin):
] ]
list_filter = [ list_filter = [
'camp',
'payment_method', 'payment_method',
'open', 'open',
'paid', 'paid',

View file

@ -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}

View 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',
),
]

View file

@ -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}))

View file

@ -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

View file

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 106 KiB

View file

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View file

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

View file

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

View file

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View file

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View file

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View file

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

View file

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View file

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View file

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View file

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View file

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

View file

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View file

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View file

Before

Width:  |  Height:  |  Size: 4 KiB

After

Width:  |  Height:  |  Size: 4 KiB

View file

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View file

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View file

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

View file

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

View file

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

View file

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

View file

Before

Width:  |  Height:  |  Size: 2 KiB

After

Width:  |  Height:  |  Size: 2 KiB

View file

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View file

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View file

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

Before

Width:  |  Height:  |  Size: 797 B

After

Width:  |  Height:  |  Size: 797 B

View file

@ -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">

Some files were not shown because too many files have changed in this diff Show more