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',
'villages',
'program',
'info',
'allauth',
'allauth.account',
'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_ROOT = local_dir('static')
STATICFILES_DIRS = [local_dir('static_src')]
@ -105,9 +70,9 @@ TEMPLATES = [
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'camps.context_processors.current_camp',
'shop.context_processors.current_order',
'shop.context_processors.user_has_tickets',
'camps.context_processors.camps',
],
},
},
@ -128,8 +93,8 @@ MIDDLEWARE_CLASSES = [
LOGIN_REDIRECT_URL = 'profiles:detail'
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend', # Login to admin with username
'allauth.account.auth_backends.AuthenticationBackend',
'django.contrib.auth.backends.ModelBackend', # Handles login to admin with username
'allauth.account.auth_backends.AuthenticationBackend', # Handles regular logins
)
ACCOUNT_AUTHENTICATION_METHOD = 'email'
@ -163,3 +128,42 @@ BANKACCOUNT_REG = env('BANKACCOUNT_REG')
BANKACCOUNT_ACCOUNT = env('BANKACCOUNT_ACCOUNT')
TICKET_CATEGORY_ID = env('TICKET_CATEGORY_ID')
DEBUG = env('DEBUG')
if DEBUG:
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
INSTALLED_APPS += ['debug_toolbar', ]
MIDDLEWARE_CLASSES += ['debug_toolbar.middleware.DebugToolbarMiddleware', ]
else:
EMAIL_HOST = env('EMAIL_HOST')
EMAIL_PORT = env('EMAIL_PORT')
EMAIL_HOST_USER = env('EMAIL_HOST_USER')
EMAIL_HOST_PASSWORD = env('EMAIL_HOST_PASSWORD')
EMAIL_USE_TLS = env('EMAIL_USE_TLS')
DEFAULT_FROM_EMAIL = env('DEFAULT_FROM_EMAIL')
SERVER_EMAIL = env('DEFAULT_FROM_EMAIL')
ARCHIVE_EMAIL = env('ARCHIVE_EMAIL')
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'mail_admins': {
'level': 'ERROR',
'class': 'django.utils.log.AdminEmailHandler',
},
'console': {
'level':'DEBUG',
'class':'logging.StreamHandler',
},
},
'loggers': {
'django.request': {
'handlers': ['mail_admins'],
'level': 'ERROR',
'propagate': True,
},
}
}

View file

@ -10,6 +10,9 @@ from django.conf.urls import include, url
from django.contrib import admin
from django.views.generic import TemplateView, RedirectView
from django.core.urlresolvers import reverse_lazy
from camps.views import *
from info.views import CampInfoView
urlpatterns = [
url(
@ -84,4 +87,57 @@ urlpatterns = [
),
url(r'^accounts/', include('allauth.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 .models import Camp, Day, Expense
from . import models
@admin.register(Expense)
class ExpenseAdmin(admin.ModelAdmin):
@admin.register(models.Camp)
class CampModelAdmin(admin.ModelAdmin):
pass
class ExpenseInlineAdmin(admin.TabularInline):
model = Expense
@admin.register(Day)
class DayAdmin(admin.ModelAdmin):
pass
class DayInlineAdmin(admin.TabularInline):
model = Day
@admin.register(Camp)
class CampAdmin(admin.ModelAdmin):
inlines = [
DayInlineAdmin,
ExpenseInlineAdmin,
]

View file

@ -1,5 +1,11 @@
from django.conf import settings
from .models import Camp
from django.utils import timezone
def current_camp(request):
return {'current_camp': Camp.objects.current()}
def camps(request):
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
from django.db import models
from django.utils.translation import ugettext_lazy as _
from utils.models import UUIDModel, CreatedUpdatedModel
from .managers import CampQuerySet
class Camp(CreatedUpdatedModel, UUIDModel):
class Meta:
verbose_name = _('Camp')
verbose_name_plural = _('Camps')
verbose_name = 'Camp'
verbose_name_plural = 'Camps'
name = models.CharField(
verbose_name=_('Name'),
help_text=_('Name of the camp, ie. Bornhack.'),
title = models.CharField(
verbose_name='Title',
help_text='Title of the camp, ie. Bornhack 2016.',
max_length=255,
)
start = models.DateTimeField(
verbose_name=_('Start date'),
help_text=_('When the camp starts.'),
unique=True,
tagline = models.CharField(
verbose_name='Tagline',
help_text='Tagline of the camp, ie. "Initial Commit"',
max_length=255,
)
end = models.DateTimeField(
verbose_name=_('End date'),
help_text=_('When the camp ends.'),
unique=True,
slug = models.SlugField(
verbose_name='Url Slug',
help_text='The url slug to use for this camp'
)
shop_open = models.BooleanField(
verbose_name=_('Shop open?'),
help_text=_('Whether the shop is open or not.'),
default=False,
buildup_start = models.DateTimeField(
verbose_name='Buildup Start date',
help_text='When the camp buildup starts.',
)
objects = CampQuerySet.as_manager()
def __str__(self):
return _('{} {}').format(
self.name,
self.start.year,
)
def create_days(self):
delta = self.end - self.start
for day_offset in range(0, delta.days + 1):
day, created = self.days.get_or_create(
date=self.start + datetime.timedelta(days=day_offset)
)
def save(self, **kwargs):
super(Camp, self).save(**kwargs)
self.create_days()
class Day(CreatedUpdatedModel, UUIDModel):
class Meta:
verbose_name = _('Day')
verbose_name_plural = _('Days')
ordering = ['date']
camp = models.ForeignKey(
'camps.Camp',
verbose_name=_('Camp'),
help_text=_('Which camp does this day belong to.'),
related_name='days',
camp_start = models.DateTimeField(
verbose_name='Start date',
help_text='When the camp starts.',
)
date = models.DateField(
verbose_name=_('Date'),
help_text=_('What date?')
camp_end = models.DateTimeField(
verbose_name='End date',
help_text='When the camp ends.',
)
def __str__(self):
return '{} ({})'.format(
self.date.strftime('%A'),
self.date
)
teardown_end = models.DateTimeField(
verbose_name='Start date',
help_text='When the camp teardown ends.',
)
def __unicode__(self):
return "%s - %s" % (self.title, self.tagline)
class Expense(CreatedUpdatedModel, UUIDModel):
class Meta:
verbose_name = _('Expense')
verbose_name_plural = _('Expenses')
verbose_name = 'Expense'
verbose_name_plural = 'Expenses'
camp = models.ForeignKey(
'camps.Camp',
verbose_name=_('Camp'),
help_text=_('The camp to which this expense relates to.'),
payment_time = models.DateTimeField(
verbose_name='Expense date/time',
help_text='The date and time this expense was paid.',
)
description = models.CharField(
verbose_name=_('Description'),
help_text=_('What this expense covers.'),
verbose_name='Description',
help_text='What this expense covers.',
max_length=255,
)
amount = models.DecimalField(
verbose_name=_('Amount'),
help_text=_('The amount of the expense.'),
dkk_amount = models.DecimalField(
verbose_name='DKK Amount',
help_text='The DKK amount of the expense.',
max_digits=7,
decimal_places=2,
)
CURRENCIES = [
('btc', 'BTC'),
('dkk', 'DKK'),
('eur', 'EUR'),
('sek', 'SEK'),
]
currency = models.CharField(
verbose_name=_('Currency'),
help_text=_('What currency the amount is in.'),
choices=CURRENCIES,
max_length=3,
receipt = models.ImageField(
verbose_name='Image of receipt',
help_text='Upload a scan or image of the receipt',
)
covered_by = models.ForeignKey(
refund_user = models.ForeignKey(
'auth.User',
verbose_name=_('Covered by'),
help_text=_('Which user, if any, covered this expense.'),
verbose_name='Refund user',
help_text='Which user, if any, covered this expense and should be refunded.',
null=True,
blank=True,
)
def __str__(self):
return _('{} {} for {}').format(
self.amount,
self.get_currency_display(),
self.camp,
)
refund_paid = models.BooleanField(
default=False,
verbose_name='Refund paid?',
help_text='Has this expense been refunded to the user?',
)

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)
class NewsItemModelAdmin(admin.ModelAdmin):
list_display = ['title', 'public', 'published_at']
list_filter = ['public']
list_display = ['title', 'published_at', 'archived']
actions = ['archive_news_items', 'unarchive_news_items']
def archive_news_items(self, request, queryset):
queryset.filter(archived=False).update(archived=True)
archive_news_items.description = 'Mark newsitem(s) as archived'
def unarchive_news_items(self, request, queryset):
queryset.filter(archived=True).update(archived=False)
unarchive_news_items.description = 'Mark newsitem(s) as not archived'

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 utils.models import CreatedUpdatedModel
from news.managers import NewsItemQuerySet
@encoding.python_2_unicode_compatible
@ -15,36 +14,33 @@ class NewsItem(CreatedUpdatedModel):
title = models.CharField(max_length=100)
content = models.TextField()
public = models.BooleanField(default=False)
published_at = models.DateTimeField()
published_at = models.DateTimeField(null=True, blank=True)
slug = models.SlugField(max_length=255, blank=True)
archived = models.BooleanField(default=False)
def __str__(self):
return self.title
def save(self, **kwargs):
if (
not self.pk or
not self.slug or
NewsItem.objects.filter(slug=self.slug).count() > 1
):
published_at_string = self.published_at.strftime('%Y-%m-%d')
base_slug = slugify(self.title)
slug = '{}-{}'.format(published_at_string, base_slug)
incrementer = 1
if self.published_at:
# if this is a new newsitem, or it doesn't have a slug, or the slug is in use on another item, create a new slug
if (not self.pk or not self.slug or NewsItem.objects.filter(slug=self.slug).count() > 1):
published_at_string = self.published_at.strftime('%Y-%m-%d')
base_slug = slugify(self.title)
slug = '{}-{}'.format(published_at_string, base_slug)
incrementer = 1
# We have to make sure that the slug won't clash with current slugs
while NewsItem.objects.filter(slug=slug).exists():
if incrementer == 1:
slug = '{}-1'.format(slug)
else:
slug = '{}-{}'.format(
'-'.join(slug.split('-')[:-1]),
incrementer
)
incrementer += 1
self.slug = slug
# We have to make sure that the slug won't clash with current slugs
while NewsItem.objects.filter(slug=slug).exists():
if incrementer == 1:
slug = '{}-1'.format(slug)
else:
slug = '{}-{}'.format(
'-'.join(slug.split('-')[:-1]),
incrementer
)
incrementer += 1
self.slug = slug
super(NewsItem, self).save(**kwargs)
objects = NewsItemQuerySet.as_manager()

View file

@ -1,33 +1,22 @@
from django.views.generic import ListView, DetailView
from django.utils import timezone
from . import models
from .models import *
class NewsIndex(ListView):
model = models.NewsItem
model = NewsItem
template_name = 'news_index.html'
context_object_name = 'news_items'
def get_queryset(self):
return self.model.objects.public()
def get_queryset(self, **kwargs):
return NewsItem.objects.filter(
published_at__isnull=False,
published_at__lt=timezone.now(),
archived=False
)
class NewsDetail(DetailView):
model = models.NewsItem
model = NewsItem
template_name = 'news_detail.html'
context_object_name = 'news_item'
def get_context_data(self, **kwargs):
context = super(NewsDetail, self).get_context_data(**kwargs)
news_item = self.get_object()
timed = news_item.published_at > timezone.now()
if news_item.public and timed:
context['not_public'] = True
context['timed'] = True
elif not news_item.public:
context['not_public'] = True
context['timed'] = False
return context

View file

@ -30,3 +30,4 @@ class Profile(CreatedUpdatedModel, UUIDModel):
def create_profile(sender, created, instance, **kwargs):
if created:
Profile.objects.create(user=instance)

View file

@ -22,17 +22,8 @@ class EventAdmin(admin.ModelAdmin):
list_display = [
'title',
'event_type',
'get_days',
'start',
'end',
]
def get_days(self, obj):
return ', '.join([
str(day.date.strftime('%a'))
for day in obj.days.all()
])
inlines = [
SpeakerInline
]

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)
abstract = models.TextField()
event_type = models.ForeignKey(EventType)
days = models.ManyToManyField('camps.Day', blank=True)
start = models.TimeField(null=True, blank=True)
end = models.TimeField(null=True, blank=True)
camp = models.ForeignKey('camps.Camp', null=True)
class Meta:
ordering = ['title']
@ -39,6 +37,20 @@ class Event(CreatedUpdatedModel):
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):
""" Person anchoring an event. """
name = models.CharField(max_length=150)

View file

@ -2,24 +2,27 @@ from collections import OrderedDict
import datetime
from django.views.generic import ListView, TemplateView, DetailView
from camp.mixins import CampViewMixin
from camps.models import Day
from . import models
class SpeakerDetailView(DetailView):
class SpeakerDetailView(CampViewMixin, DetailView):
model = models.Speaker
template_name = 'speaker_detail.html'
class SpeakerListView(ListView):
class SpeakerListView(CampViewMixin, ListView):
model = models.Speaker
template_name = 'speaker_list.html'
class EventListView(ListView):
class EventListView(CampViewMixin, ListView):
model = models.Event
template_name = 'event_list.html'
class ProgramOverviewView(ListView):
class ProgramOverviewView(CampViewMixin, ListView):
model = models.Event
template_name = 'program_overview.html'
@ -54,7 +57,7 @@ class ProgramOverviewView(ListView):
return context
class ProgramDayView(TemplateView):
class ProgramDayView(CampViewMixin, TemplateView):
template_name = 'program_day.html'
def get_context_data(self, **kwargs):
@ -71,7 +74,7 @@ class ProgramDayView(TemplateView):
return context
class EventDetailView(DetailView):
class EventDetailView(CampViewMixin, DetailView):
model = models.Event
template_name = 'program_event_detail.html'
@ -80,3 +83,5 @@ class EventDetailView(DetailView):
# TODO: date__year is hardcoded here - need fix for 2017 :P
context['days'] = Day.objects.filter(date__year=2016)
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
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
Unidecode==0.4.19
django-debug-toolbar>=1.6
django-bootstrap3==7.1.0
django-debug-toolbar==1.6
django-environ==0.4.1
django-wkhtmltopdf==3.1.0
future==0.16.0
html5lib==0.9999999
oauthlib==2.0.1
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 = [
'camp',
'payment_method',
'open',
'paid',

View file

@ -20,3 +20,4 @@ def user_has_tickets(request):
).exists():
has_tickets = True
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):
camp = models.ForeignKey(
'camps.Camp',
verbose_name=_('Camp'),
help_text=_('The camp this custom order is for.'),
)
text = models.TextField(
help_text=_('The invoice text')
)
@ -78,12 +72,6 @@ class Order(CreatedUpdatedModel):
default=True,
)
camp = models.ForeignKey(
'camps.Camp',
verbose_name=_('Camp'),
help_text=_('The camp this shop order is for.'),
)
CREDIT_CARD = 'credit_card'
BLOCKCHAIN = 'blockchain'
BANK_TRANSFER = 'bank_transfer'
@ -168,7 +156,7 @@ class Order(CreatedUpdatedModel):
@property
def description(self):
return "BornHack %s order #%s" % (self.camp.start.year, self.pk)
return "Order #%s" % self.pk
def get_absolute_url(self):
return str(reverse_lazy('shop:order_detail', kwargs={'pk': self.pk}))

View file

@ -20,7 +20,6 @@ from django.views.decorators.csrf import csrf_exempt
from django.utils.dateparse import parse_datetime
from django.utils import timezone
from camps.models import Camp
from shop.models import (
Order,
Product,
@ -234,7 +233,6 @@ class ProductDetailView(FormView, DetailView):
# no open order - open a new one
order = Order.objects.create(
user=self.request.user,
camp=Camp.objects.current()
)
# get product from kwargs

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">
<ul class="nav navbar-nav">
<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 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 %}
<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>
{% if user.is_authenticated %}
<li><a href="{% url 'profiles:detail' %}">Profile</a></li>
@ -55,7 +67,6 @@
</ul>
</div>
</div>
</nav>
<div id="main" class="container container-fluid">

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