Merge pull request #80 from bornhack/multicamp
Finally merge multicamp support
This commit is contained in:
commit
b72cf60419
17
Makefile
17
Makefile
|
@ -1,17 +0,0 @@
|
|||
SETTINGS = bornhack.settings.development
|
||||
|
||||
all: migrate superuser run
|
||||
|
||||
migrations:
|
||||
./dev.sh makemigrations --settings=$(SETTINGS)
|
||||
|
||||
migrate:
|
||||
./dev.sh migrate --settings=$(SETTINGS)
|
||||
|
||||
superuser:
|
||||
./dev.sh createsuperuser --settings=$(SETTINGS)
|
||||
|
||||
run:
|
||||
./dev.sh runserver --settings=$(SETTINGS)
|
||||
|
||||
.PHONY = all migrations migrate superuser run
|
39
README.md
39
README.md
|
@ -6,31 +6,50 @@ Django project to power Bornhack. Features include news, villages, webshop, and
|
|||
|
||||
### Virtualenv
|
||||
Create a Python 2.7 virtual environment and activate it:
|
||||
$ virtualenv venv
|
||||
$ source venv/bin/activate
|
||||
```
|
||||
$ virtualenv venv
|
||||
$ source venv/bin/activate
|
||||
```
|
||||
|
||||
### System libraries
|
||||
Install system dependencies (method depends on OS):
|
||||
- postgresql headers (for psychopg2)
|
||||
- postgresql headers (for psychopg2):
|
||||
- Debian: ?
|
||||
- FreeBSD: ?
|
||||
- libjpeg (for pdf generation)
|
||||
- Debian: libjpeg-dev
|
||||
- FreeBSD: ?
|
||||
|
||||
### Python packages
|
||||
Install pip packages (pick either development or production):
|
||||
(venv) $ pip install -r requirements/development.txt
|
||||
(venv) $ pip install -r requirements/production.txt
|
||||
Install pip packages:
|
||||
```
|
||||
(venv) $ pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### Configuration file
|
||||
Copy environment file template and change settings:
|
||||
Copy environment file template and change settings as needed:
|
||||
```
|
||||
(venv) $ cp bornhack/settings/env.dist bornhack/settings/.env
|
||||
```
|
||||
|
||||
### Database
|
||||
Is this a new installation? Initialize the database:
|
||||
(venv) $ ./manage.py migrate --settings=bornhack.settings.development
|
||||
(venv) $ ./manage.py createsuperuser --settings=bornhack.settings.development
|
||||
```
|
||||
(venv) $ ./manage.py migrate
|
||||
```
|
||||
|
||||
Is this for local development? Bootstrap the database with dummy data and users:
|
||||
```
|
||||
(venv) $ ./manage.py bootstrap-devsite
|
||||
```
|
||||
|
||||
### Done
|
||||
Is this for local development? Start the Django devserver:
|
||||
(venv) $ ./manage.py runserver --settings=bornhack.settings.development
|
||||
```
|
||||
(venv) $ ./manage.py runserver
|
||||
```
|
||||
|
||||
Otherwise start uwsgi or similar to serve the application.
|
||||
|
||||
Enjoy!
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
### set database url and allowed_hosts here
|
||||
DATABASE_URL=postgres://username:password@host:port/database
|
||||
ALLOWED_HOSTS=localhost,127.0.0.1
|
||||
DEBUG=True
|
||||
|
||||
### changes below here are only needed for production
|
||||
SECRET_KEY=somethingverysecretandunique
|
||||
|
@ -29,4 +30,7 @@ BANKACCOUNT_IBAN='123'
|
|||
BANKACCOUNT_SWIFTBIC='123'
|
||||
BANKACCOUNT_REG='123'
|
||||
BANKACCOUNT_ACCOUNT='123'
|
||||
TIME_ZONE='Europe/Copenhagen'
|
||||
SCHEDULE_MIDNIGHT_OFFSET_HOURS=6
|
||||
SCHEDULE_TIMESLOT_LENGTH_MINUTES=30
|
||||
|
|
@ -42,6 +42,8 @@ INSTALLED_APPS = [
|
|||
'utils',
|
||||
'villages',
|
||||
'program',
|
||||
'info',
|
||||
'sponsors',
|
||||
|
||||
'allauth',
|
||||
'allauth.account',
|
||||
|
@ -53,10 +55,14 @@ STATIC_ROOT = local_dir('static')
|
|||
STATICFILES_DIRS = [local_dir('static_src')]
|
||||
MEDIA_ROOT = env('MEDIA_ROOT')
|
||||
LANGUAGE_CODE = 'en-us'
|
||||
TIME_ZONE = 'UTC'
|
||||
USE_I18N = True
|
||||
USE_L10N = True
|
||||
TIME_ZONE = env('TIME_ZONE')
|
||||
#USE_I18N = True
|
||||
#USE_L10N = True
|
||||
USE_TZ = True
|
||||
SHORT_DATE_FORMAT = 'd/m-Y'
|
||||
DATE_FORMAT = 'd/m-Y'
|
||||
DATETIME_FORMAT = 'd/m-Y H:i'
|
||||
TIME_FORMAT = 'H:i'
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
|
@ -69,9 +75,9 @@ TEMPLATES = [
|
|||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
'camps.context_processors.current_camp',
|
||||
'shop.context_processors.current_order',
|
||||
'shop.context_processors.user_has_tickets',
|
||||
'camps.context_processors.camp',
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -92,8 +98,8 @@ MIDDLEWARE_CLASSES = [
|
|||
LOGIN_REDIRECT_URL = 'profiles:detail'
|
||||
|
||||
AUTHENTICATION_BACKENDS = (
|
||||
'django.contrib.auth.backends.ModelBackend', # Login to admin with username
|
||||
'allauth.account.auth_backends.AuthenticationBackend',
|
||||
'django.contrib.auth.backends.ModelBackend', # Handles login to admin with username
|
||||
'allauth.account.auth_backends.AuthenticationBackend', # Handles regular logins
|
||||
)
|
||||
|
||||
ACCOUNT_AUTHENTICATION_METHOD = 'email'
|
||||
|
@ -127,3 +133,46 @@ BANKACCOUNT_REG = env('BANKACCOUNT_REG')
|
|||
BANKACCOUNT_ACCOUNT = env('BANKACCOUNT_ACCOUNT')
|
||||
|
||||
TICKET_CATEGORY_ID = env('TICKET_CATEGORY_ID')
|
||||
|
||||
DEBUG = env('DEBUG')
|
||||
if DEBUG:
|
||||
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
||||
INSTALLED_APPS += ['debug_toolbar', ]
|
||||
MIDDLEWARE_CLASSES += ['debug_toolbar.middleware.DebugToolbarMiddleware', ]
|
||||
|
||||
else:
|
||||
EMAIL_HOST = env('EMAIL_HOST')
|
||||
EMAIL_PORT = env('EMAIL_PORT')
|
||||
EMAIL_HOST_USER = env('EMAIL_HOST_USER')
|
||||
EMAIL_HOST_PASSWORD = env('EMAIL_HOST_PASSWORD')
|
||||
EMAIL_USE_TLS = env('EMAIL_USE_TLS')
|
||||
DEFAULT_FROM_EMAIL = env('DEFAULT_FROM_EMAIL')
|
||||
SERVER_EMAIL = env('DEFAULT_FROM_EMAIL')
|
||||
ARCHIVE_EMAIL = env('ARCHIVE_EMAIL')
|
||||
|
||||
LOGGING = {
|
||||
'version': 1,
|
||||
'disable_existing_loggers': False,
|
||||
'handlers': {
|
||||
'mail_admins': {
|
||||
'level': 'ERROR',
|
||||
'class': 'django.utils.log.AdminEmailHandler',
|
||||
},
|
||||
'console': {
|
||||
'level':'DEBUG',
|
||||
'class':'logging.StreamHandler',
|
||||
},
|
||||
},
|
||||
'loggers': {
|
||||
'django.request': {
|
||||
'handlers': ['mail_admins'],
|
||||
'level': 'ERROR',
|
||||
'propagate': True,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
# schedule settings
|
||||
SCHEDULE_MIDNIGHT_OFFSET_HOURS=int(env('SCHEDULE_MIDNIGHT_OFFSET_HOURS'))
|
||||
SCHEDULE_TIMESLOT_LENGTH_MINUTES=int(env('SCHEDULE_TIMESLOT_LENGTH_MINUTES'))
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
from .base import *
|
||||
import environ
|
||||
env = environ.Env()
|
||||
environ.Env.read_env()
|
||||
|
||||
DEBUG = True
|
||||
|
||||
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
||||
|
||||
# INSTALLED_APPS += ['debug_toolbar', ]
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
from .base import *
|
||||
import environ
|
||||
env = environ.Env()
|
||||
environ.Env.read_env()
|
||||
|
||||
DEBUG = False
|
||||
|
||||
EMAIL_HOST = env('EMAIL_HOST')
|
||||
EMAIL_PORT = env('EMAIL_PORT')
|
||||
EMAIL_HOST_USER = env('EMAIL_HOST_USER')
|
||||
EMAIL_HOST_PASSWORD = env('EMAIL_HOST_PASSWORD')
|
||||
EMAIL_USE_TLS = env('EMAIL_USE_TLS')
|
||||
DEFAULT_FROM_EMAIL = env('DEFAULT_FROM_EMAIL')
|
||||
SERVER_EMAIL = env('DEFAULT_FROM_EMAIL')
|
||||
ARCHIVE_EMAIL = env('ARCHIVE_EMAIL')
|
||||
|
||||
LOGGING = {
|
||||
'version': 1,
|
||||
'disable_existing_loggers': False,
|
||||
'handlers': {
|
||||
'mail_admins': {
|
||||
'level': 'ERROR',
|
||||
'class': 'django.utils.log.AdminEmailHandler',
|
||||
},
|
||||
'console': {
|
||||
'level':'DEBUG',
|
||||
'class':'logging.StreamHandler',
|
||||
},
|
||||
},
|
||||
'loggers': {
|
||||
'django.request': {
|
||||
'handlers': ['mail_admins'],
|
||||
'level': 'ERROR',
|
||||
'propagate': True,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -1,132 +0,0 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load static from staticfiles %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<img src="{% static 'img/logo.png' %}" class="img-responsive"
|
||||
id="front-logo" />
|
||||
|
||||
<br />
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-8 text-container">
|
||||
<div class="lead">
|
||||
<strong>Bornhack</strong> is an annual 7 day outdoor tent camping festival
|
||||
where people with an interest in technology, security and society
|
||||
come together to celebrate technology, socialise, learn and <strong>have fun</strong>.
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<a href="https://people.bornhack.org/tyk/ohm_cccamp_pics/IMG_20150812_162831.jpg">
|
||||
<img class="img-thumbnail"
|
||||
src="https://people.bornhack.org/tyk/ohm_cccamp_pics/IMG_20150812_162831_tn.jpg"
|
||||
alt="Tents at CCCamp2015" title="Tents at CCCamp2015">
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br />
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<a href="https://people.bornhack.org/tyk/ohm_cccamp_pics/jarlsgaard.jpg">
|
||||
<img class="img-thumbnail"
|
||||
src="https://people.bornhack.org/tyk/ohm_cccamp_pics/jarlsgaard_tn.jpg"
|
||||
alt="Jarlsgaard from above" title="Jarlsgaard from above">
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-8 text-container">
|
||||
<div class="lead">
|
||||
The first Bornhack took place from <strong>August 27 to September 3rd 2016</strong>
|
||||
on the Danish island of <strong>Bornholm</strong>. We are preparing
|
||||
<strong>BornHack 2017</strong> which will be from <strong>22nd to 27th of August 2017</strong>.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br />
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-8 text-container">
|
||||
<p class="lead">
|
||||
Bornhack is a <strong>participatory</strong> event where we expect people
|
||||
to come up with ideas and projects, and help make the content. We will reach out to
|
||||
people to ensure interesting, high quality talks and workshops.
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<a href="https://people.bornhack.org/tyk/ohm_cccamp_pics/_MG_5755.jpg">
|
||||
<img class="img-thumbnail"
|
||||
src="https://people.bornhack.org/tyk/ohm_cccamp_pics/_MG_5755_tn.jpg"
|
||||
alt="Hacking in Baconsvin village at OHM2013" title="Hacking in Baconsvin village at OHM2013">
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br />
|
||||
|
||||
<p class="lead">
|
||||
You are very welcome to ask questions and show your interest on our different channels:
|
||||
</p>
|
||||
{% include 'includes/contact.html' %}
|
||||
<p class="lead">
|
||||
Follow and like us to get updates about BornHack.
|
||||
</p>
|
||||
|
||||
<p class="lead">
|
||||
We hope you want to be a part of Bornhack. Ticket sales for 2017 will be open soon!
|
||||
</p>
|
||||
|
||||
<div class="regards-and-button">
|
||||
<p class="lead">
|
||||
Sincerely<br />
|
||||
The Bornhack Team
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://people.bornhack.org/tyk/ohm_cccamp_pics/IMG_20150815_141706.jpg">
|
||||
<img class="img-thumbnail"
|
||||
src="https://people.bornhack.org/tyk/ohm_cccamp_pics/IMG_20150815_141706_tn.jpg"
|
||||
alt="Hacking sign at CCCamp2015" title="Hacking sign at CCCamp2015">
|
||||
</a>
|
||||
<a href="https://people.bornhack.org/tyk/ohm_cccamp_pics/IMG_20150812_163606.jpg">
|
||||
<img class="img-thumbnail" src="https://people.bornhack.org/tyk/ohm_cccamp_pics/IMG_20150812_163606_tn.jpg"
|
||||
alt="Entrance to Baconsvin village at CCCamp2015" title="Entrance to Baconsvin village at CCCamp2015">
|
||||
</a>
|
||||
<a href="https://people.bornhack.org/tyk/ohm_cccamp_pics/IMG_20150812_201635.jpg">
|
||||
<img class="img-thumbnail" src="https://people.bornhack.org/tyk/ohm_cccamp_pics/IMG_20150812_201635_tn.jpg"
|
||||
alt="Dome being built at CCCamp2015" title="Dome being built at CCCamp2015">
|
||||
</a>
|
||||
<a href="https://people.bornhack.org/tyk/ohm_cccamp_pics/_MG_5746.jpg">
|
||||
<img class="img-thumbnail" src="https://people.bornhack.org/tyk/ohm_cccamp_pics/_MG_5746_tn.jpg"
|
||||
alt="Person swinging lights in the night at OHM2013" title="Person swinging lights in the night at OHM2013">
|
||||
</a>
|
||||
<a href="https://people.bornhack.org/tyk/ohm_cccamp_pics/IMG_20150814_145204.jpg">
|
||||
<img class="img-thumbnail" src="https://people.bornhack.org/tyk/ohm_cccamp_pics/IMG_20150814_145204_tn.jpg"
|
||||
alt="Queer Feminist Geeks village at CCCamp2015" title="Queer Feminist Geeks village at CCCamp2015">
|
||||
</a>
|
||||
<a href="https://people.bornhack.org/tyk/ohm_cccamp_pics/IMG_20150815_154027.jpg">
|
||||
<img class="img-thumbnail" src="https://people.bornhack.org/tyk/ohm_cccamp_pics/IMG_20150815_154027_tn.jpg"
|
||||
alt="Dome at CCCamp2015" title="Dome at CCCamp2015">
|
||||
</a>
|
||||
<a href="https://people.bornhack.org/tyk/ohm_cccamp_pics/_MG_5736.jpg">
|
||||
<img class="img-thumbnail" src="https://people.bornhack.org/tyk/ohm_cccamp_pics/_MG_5736_tn.jpg"
|
||||
alt="Lights at night at OHM2013" title="Lights at night at OHM2013">
|
||||
</a>
|
||||
<a href="https://people.bornhack.org/tyk/ohm_cccamp_pics/IMG_20150815_154119.jpg">
|
||||
<img class="img-thumbnail" src="https://people.bornhack.org/tyk/ohm_cccamp_pics/IMG_20150815_154119_tn.jpg"
|
||||
alt="Free Chelsea Manning sign at CCCamp2015" title="Free Chelsea Manning sign at CCCamp2015">
|
||||
</a>
|
||||
<a href="https://people.bornhack.org/tyk/ohm_cccamp_pics/IMG_20150816_153404.jpg">
|
||||
<img class="img-thumbnail" src="https://people.bornhack.org/tyk/ohm_cccamp_pics/IMG_20150816_153404_tn.jpg"
|
||||
alt="Swedish Embassy sign at CCCamp2015" title="Swedish Embassy sign at CCCamp2015">
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
129
bornhack/urls.py
129
bornhack/urls.py
|
@ -10,6 +10,11 @@ from django.conf.urls import include, url
|
|||
from django.contrib import admin
|
||||
from django.views.generic import TemplateView, RedirectView
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from camps.views import *
|
||||
from info.views import *
|
||||
from villages.views import *
|
||||
from program.views import *
|
||||
from sponsors.views import *
|
||||
|
||||
urlpatterns = [
|
||||
url(
|
||||
|
@ -24,24 +29,11 @@ urlpatterns = [
|
|||
r'^news/',
|
||||
include('news.urls', namespace='news')
|
||||
),
|
||||
url(
|
||||
r'^villages/',
|
||||
include('villages.urls', namespace='villages')
|
||||
),
|
||||
url(
|
||||
r'^schedule/',
|
||||
include('program.urls', namespace='schedule')
|
||||
),
|
||||
url(
|
||||
r'^$',
|
||||
TemplateView.as_view(template_name='frontpage.html'),
|
||||
name='frontpage'
|
||||
),
|
||||
url(
|
||||
r'^info/',
|
||||
TemplateView.as_view(template_name='info.html'),
|
||||
name='info'
|
||||
),
|
||||
url(
|
||||
r'^contact/',
|
||||
TemplateView.as_view(template_name='contact.html'),
|
||||
|
@ -52,16 +44,6 @@ urlpatterns = [
|
|||
TemplateView.as_view(template_name='coc.html'),
|
||||
name='conduct'
|
||||
),
|
||||
url(
|
||||
r'^sponsors/',
|
||||
TemplateView.as_view(template_name='sponsors.html'),
|
||||
name='call-for-sponsors'
|
||||
),
|
||||
url(
|
||||
r'^speakers/',
|
||||
TemplateView.as_view(template_name='speakers.html'),
|
||||
name='call-for-speakers'
|
||||
),
|
||||
url(
|
||||
r'^login/$',
|
||||
LoginView.as_view(),
|
||||
|
@ -84,4 +66,105 @@ urlpatterns = [
|
|||
),
|
||||
url(r'^accounts/', include('allauth.urls')),
|
||||
url(r'^admin/', include(admin.site.urls)),
|
||||
|
||||
url(
|
||||
r'^camps/$',
|
||||
CampListView.as_view(),
|
||||
name='camp_list'
|
||||
),
|
||||
|
||||
# camp specific urls below here
|
||||
|
||||
url(
|
||||
r'(?P<camp_slug>[-_\w+]+)/', include([
|
||||
url(
|
||||
r'^$',
|
||||
CampDetailView.as_view(),
|
||||
name='camp_detail'
|
||||
),
|
||||
|
||||
url(
|
||||
r'^info/$',
|
||||
CampInfoView.as_view(),
|
||||
name='info'
|
||||
),
|
||||
|
||||
url(
|
||||
r'^schedule/', include([
|
||||
url(
|
||||
r'^(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})/$',
|
||||
ProgramDayView.as_view(),
|
||||
name='schedule_day'
|
||||
),
|
||||
url(
|
||||
r'^$',
|
||||
ProgramOverviewView.as_view(),
|
||||
name='schedule_index'
|
||||
),
|
||||
url(
|
||||
r'^speakers/$',
|
||||
SpeakerListView.as_view(),
|
||||
name='speaker_index'
|
||||
),
|
||||
url(
|
||||
r'^speakers/(?P<slug>[-_\w+]+)/$',
|
||||
SpeakerDetailView.as_view(),
|
||||
name='speaker_detail'
|
||||
),
|
||||
url(
|
||||
r'^events/$',
|
||||
EventListView.as_view(),
|
||||
name='event_index'
|
||||
),
|
||||
url(
|
||||
r'^call-for-speakers/$',
|
||||
CallForSpeakersView.as_view(),
|
||||
name='call_for_speakers'
|
||||
),
|
||||
url(
|
||||
r'^(?P<slug>[-_\w+]+)/$',
|
||||
EventDetailView.as_view(),
|
||||
name='event_detail'
|
||||
),
|
||||
])
|
||||
),
|
||||
|
||||
url(
|
||||
r'^sponsors/$',
|
||||
SponsorView.as_view(),
|
||||
name='sponsors'
|
||||
),
|
||||
|
||||
url(
|
||||
r'^villages/', include([
|
||||
url(
|
||||
r'^$',
|
||||
VillageListView.as_view(),
|
||||
name='village_list'
|
||||
),
|
||||
url(
|
||||
r'create/$',
|
||||
VillageCreateView.as_view(),
|
||||
name='village_create'
|
||||
),
|
||||
url(
|
||||
r'(?P<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'
|
||||
),
|
||||
])
|
||||
),
|
||||
])
|
||||
)
|
||||
]
|
||||
|
||||
|
|
|
@ -11,6 +11,6 @@ import os
|
|||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "bornhack.settings.production")
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "bornhack.settings")
|
||||
|
||||
application = get_wsgi_application()
|
||||
|
|
|
@ -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,
|
||||
]
|
||||
|
|
|
@ -1,5 +1,23 @@
|
|||
from django.conf import settings
|
||||
from .models import Camp
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
def current_camp(request):
|
||||
return {'current_camp': Camp.objects.current()}
|
||||
def camp(request):
|
||||
"""
|
||||
if we have a camp_slug url component then get the "current" Camp object.
|
||||
Return it after adding the slug to request.session along with a "camps"
|
||||
queryset containing all camps (used to build the menu and such)
|
||||
"""
|
||||
if 'camp_slug' in request.resolver_match.kwargs:
|
||||
camp = Camp.objects.get(slug=request.resolver_match.kwargs['camp_slug'])
|
||||
request.session['campslug'] = camp.slug
|
||||
else:
|
||||
request.session['campslug'] = None
|
||||
camp = None
|
||||
|
||||
return {
|
||||
'camps': Camp.objects.all().order_by('-camp'),
|
||||
'camp': camp
|
||||
}
|
||||
|
||||
|
|
|
@ -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
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
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
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
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,
|
||||
),
|
||||
]
|
27
camps/migrations/0011_auto_20161228_1750.py
Normal file
27
camps/migrations/0011_auto_20161228_1750.py
Normal file
|
@ -0,0 +1,27 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.4 on 2016-12-28 17:50
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('camps', '0010_auto_20161220_1714'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='camp',
|
||||
name='logo_large',
|
||||
field=models.CharField(default='', help_text=b'The filename of the large logo to use on the frontpage of this camp', max_length=100, verbose_name=b'Large logo for this camp'),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='camp',
|
||||
name='logo_small',
|
||||
field=models.CharField(default='', help_text=b'The filename of the small logo to use in the top of the page for this camp', max_length=100, verbose_name=b'Small logo for this camp'),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
23
camps/migrations/0012_auto_20161228_2312.py
Normal file
23
camps/migrations/0012_auto_20161228_2312.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.4 on 2016-12-28 23:12
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('camps', '0011_auto_20161228_1750'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='camp',
|
||||
name='logo_large',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='camp',
|
||||
name='logo_small',
|
||||
),
|
||||
]
|
47
camps/migrations/0013_auto_20161229_2201.py
Normal file
47
camps/migrations/0013_auto_20161229_2201.py
Normal file
|
@ -0,0 +1,47 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.4 on 2016-12-29 22:01
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import django.contrib.postgres.fields.ranges
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('camps', '0012_auto_20161228_2312'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='camp',
|
||||
name='buildup_start',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='camp',
|
||||
name='camp_end',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='camp',
|
||||
name='camp_start',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='camp',
|
||||
name='teardown_end',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='camp',
|
||||
name='buildup',
|
||||
field=django.contrib.postgres.fields.ranges.DateTimeRangeField(help_text=b'The camp buildup period.', null=True, verbose_name=b'Buildup Period'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='camp',
|
||||
name='camp',
|
||||
field=django.contrib.postgres.fields.ranges.DateTimeRangeField(help_text=b'The camp period.', null=True, verbose_name=b'Camp Period'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='camp',
|
||||
name='teardown',
|
||||
field=django.contrib.postgres.fields.ranges.DateTimeRangeField(help_text=b'The camp teardown period.', null=True, verbose_name=b'Teardown period'),
|
||||
),
|
||||
]
|
31
camps/migrations/0014_auto_20161229_2202.py
Normal file
31
camps/migrations/0014_auto_20161229_2202.py
Normal file
|
@ -0,0 +1,31 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.4 on 2016-12-29 22:02
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import django.contrib.postgres.fields.ranges
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('camps', '0013_auto_20161229_2201'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='camp',
|
||||
name='buildup',
|
||||
field=django.contrib.postgres.fields.ranges.DateTimeRangeField(help_text=b'The camp buildup period.', verbose_name=b'Buildup Period'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='camp',
|
||||
name='camp',
|
||||
field=django.contrib.postgres.fields.ranges.DateTimeRangeField(help_text=b'The camp period.', verbose_name=b'Camp Period'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='camp',
|
||||
name='teardown',
|
||||
field=django.contrib.postgres.fields.ranges.DateTimeRangeField(help_text=b'The camp teardown period.', verbose_name=b'Teardown period'),
|
||||
),
|
||||
]
|
22
camps/migrations/0015_auto_20170116_1634.py
Normal file
22
camps/migrations/0015_auto_20170116_1634.py
Normal file
|
@ -0,0 +1,22 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.4 on 2017-01-16 16:34
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('camps', '0014_auto_20161229_2202'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='expense',
|
||||
name='refund_user',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='Expense',
|
||||
),
|
||||
]
|
20
camps/migrations/0016_camp_description.py
Normal file
20
camps/migrations/0016_camp_description.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.4 on 2017-01-16 16:37
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('camps', '0015_auto_20170116_1634'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='camp',
|
||||
name='description',
|
||||
field=models.TextField(default=b'', help_text=b'Description of the camp, shown on the camp frontpage. HTML and markdown supported.', verbose_name=b'Description'),
|
||||
),
|
||||
]
|
19
camps/migrations/0017_remove_camp_description.py
Normal file
19
camps/migrations/0017_remove_camp_description.py
Normal file
|
@ -0,0 +1,19 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.4 on 2017-01-16 21:59
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('camps', '0016_camp_description'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='camp',
|
||||
name='description',
|
||||
),
|
||||
]
|
14
camps/mixins.py
Normal file
14
camps/mixins.py
Normal file
|
@ -0,0 +1,14 @@
|
|||
from camps.models import Camp
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
|
||||
class CampViewMixin(object):
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
self.camp = get_object_or_404(Camp, slug=self.kwargs['camp_slug'])
|
||||
return super(CampViewMixin, self).dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super(CampViewMixin, self).get_queryset()
|
||||
return queryset.filter(camp=self.camp)
|
||||
|
||||
|
253
camps/models.py
253
camps/models.py
|
@ -1,135 +1,158 @@
|
|||
import datetime
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from utils.models import UUIDModel, CreatedUpdatedModel
|
||||
|
||||
from .managers import CampQuerySet
|
||||
from program.models import EventType
|
||||
from django.contrib.postgres.fields import DateTimeRangeField
|
||||
from psycopg2.extras import DateTimeTZRange
|
||||
from django.core.exceptions import ValidationError
|
||||
from datetime import timedelta
|
||||
|
||||
|
||||
class Camp(CreatedUpdatedModel, UUIDModel):
|
||||
class Meta:
|
||||
verbose_name = _('Camp')
|
||||
verbose_name_plural = _('Camps')
|
||||
verbose_name = 'Camp'
|
||||
verbose_name_plural = 'Camps'
|
||||
|
||||
name = models.CharField(
|
||||
verbose_name=_('Name'),
|
||||
help_text=_('Name of the camp, ie. Bornhack.'),
|
||||
title = models.CharField(
|
||||
verbose_name='Title',
|
||||
help_text='Title of the camp, ie. Bornhack 2016.',
|
||||
max_length=255,
|
||||
)
|
||||
|
||||
start = models.DateTimeField(
|
||||
verbose_name=_('Start date'),
|
||||
help_text=_('When the camp starts.'),
|
||||
unique=True,
|
||||
)
|
||||
|
||||
end = models.DateTimeField(
|
||||
verbose_name=_('End date'),
|
||||
help_text=_('When the camp ends.'),
|
||||
unique=True,
|
||||
)
|
||||
|
||||
shop_open = models.BooleanField(
|
||||
verbose_name=_('Shop open?'),
|
||||
help_text=_('Whether the shop is open or not.'),
|
||||
default=False,
|
||||
)
|
||||
|
||||
objects = CampQuerySet.as_manager()
|
||||
|
||||
def __str__(self):
|
||||
return _('{} {}').format(
|
||||
self.name,
|
||||
self.start.year,
|
||||
)
|
||||
|
||||
def create_days(self):
|
||||
delta = self.end - self.start
|
||||
for day_offset in range(0, delta.days + 1):
|
||||
day, created = self.days.get_or_create(
|
||||
date=self.start + datetime.timedelta(days=day_offset)
|
||||
)
|
||||
|
||||
def save(self, **kwargs):
|
||||
super(Camp, self).save(**kwargs)
|
||||
self.create_days()
|
||||
|
||||
|
||||
class Day(CreatedUpdatedModel, UUIDModel):
|
||||
class Meta:
|
||||
verbose_name = _('Day')
|
||||
verbose_name_plural = _('Days')
|
||||
ordering = ['date']
|
||||
|
||||
camp = models.ForeignKey(
|
||||
'camps.Camp',
|
||||
verbose_name=_('Camp'),
|
||||
help_text=_('Which camp does this day belong to.'),
|
||||
related_name='days',
|
||||
)
|
||||
|
||||
date = models.DateField(
|
||||
verbose_name=_('Date'),
|
||||
help_text=_('What date?')
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return '{} ({})'.format(
|
||||
self.date.strftime('%A'),
|
||||
self.date
|
||||
)
|
||||
|
||||
|
||||
class Expense(CreatedUpdatedModel, UUIDModel):
|
||||
class Meta:
|
||||
verbose_name = _('Expense')
|
||||
verbose_name_plural = _('Expenses')
|
||||
|
||||
camp = models.ForeignKey(
|
||||
'camps.Camp',
|
||||
verbose_name=_('Camp'),
|
||||
help_text=_('The camp to which this expense relates to.'),
|
||||
)
|
||||
|
||||
description = models.CharField(
|
||||
verbose_name=_('Description'),
|
||||
help_text=_('What this expense covers.'),
|
||||
tagline = models.CharField(
|
||||
verbose_name='Tagline',
|
||||
help_text='Tagline of the camp, ie. "Initial Commit"',
|
||||
max_length=255,
|
||||
)
|
||||
|
||||
amount = models.DecimalField(
|
||||
verbose_name=_('Amount'),
|
||||
help_text=_('The amount of the expense.'),
|
||||
max_digits=7,
|
||||
decimal_places=2,
|
||||
slug = models.SlugField(
|
||||
verbose_name='Url Slug',
|
||||
help_text='The url slug to use for this camp'
|
||||
)
|
||||
|
||||
CURRENCIES = [
|
||||
('btc', 'BTC'),
|
||||
('dkk', 'DKK'),
|
||||
('eur', 'EUR'),
|
||||
('sek', 'SEK'),
|
||||
]
|
||||
|
||||
currency = models.CharField(
|
||||
verbose_name=_('Currency'),
|
||||
help_text=_('What currency the amount is in.'),
|
||||
choices=CURRENCIES,
|
||||
max_length=3,
|
||||
buildup = DateTimeRangeField(
|
||||
verbose_name='Buildup Period',
|
||||
help_text='The camp buildup period.',
|
||||
)
|
||||
|
||||
covered_by = models.ForeignKey(
|
||||
'auth.User',
|
||||
verbose_name=_('Covered by'),
|
||||
help_text=_('Which user, if any, covered this expense.'),
|
||||
null=True,
|
||||
blank=True,
|
||||
camp = DateTimeRangeField(
|
||||
verbose_name='Camp Period',
|
||||
help_text='The camp period.',
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return _('{} {} for {}').format(
|
||||
self.amount,
|
||||
self.get_currency_display(),
|
||||
self.camp,
|
||||
)
|
||||
teardown = DateTimeRangeField(
|
||||
verbose_name='Teardown period',
|
||||
help_text='The camp teardown period.',
|
||||
)
|
||||
|
||||
def clean(self):
|
||||
''' Make sure the dates make sense - meaning no overlaps and buildup before camp before teardown '''
|
||||
errors = []
|
||||
# sanity checking for buildup
|
||||
if self.buildup.lower > self.buildup.upper:
|
||||
errors.append(ValidationError({'buildup', 'Start of buildup must be before end of buildup'}))
|
||||
|
||||
# sanity checking for camp
|
||||
if self.camp.lower > self.camp.upper:
|
||||
errors.append(ValidationError({'camp', 'Start of camp must be before end of camp'}))
|
||||
|
||||
# sanity checking for teardown
|
||||
if self.teardown.lower > self.teardown.upper:
|
||||
errors.append(ValidationError({'teardown', 'Start of teardown must be before end of teardown'}))
|
||||
|
||||
# check for overlaps buildup vs. camp
|
||||
if self.buildup.upper > self.camp.lower:
|
||||
msg = "End of buildup must not be after camp start"
|
||||
errors.append(ValidationError({'buildup', msg}))
|
||||
errors.append(ValidationError({'camp', msg}))
|
||||
|
||||
# check for overlaps camp vs. teardown
|
||||
if self.camp.upper > self.teardown.lower:
|
||||
msg = "End of camp must not be after teardown start"
|
||||
errors.append(ValidationError({'camp', msg}))
|
||||
errors.append(ValidationError({'teardown', msg}))
|
||||
|
||||
if errors:
|
||||
raise ValidationError(errors)
|
||||
|
||||
def __unicode__(self):
|
||||
return "%s - %s" % (self.title, self.tagline)
|
||||
|
||||
@property
|
||||
def event_types(self):
|
||||
# return all event types with at least one event in this camp
|
||||
return EventType.objects.filter(event__instances__isnull=False, event__camp=self).distinct()
|
||||
|
||||
@property
|
||||
def logo_small(self):
|
||||
return 'img/%(slug)s/logo/%(slug)s-logo-small.png' % {'slug': self.slug}
|
||||
|
||||
@property
|
||||
def logo_large(self):
|
||||
return 'img/%(slug)s/logo/%(slug)s-logo-large.png' % {'slug': self.slug}
|
||||
|
||||
def get_days(self, camppart):
|
||||
'''
|
||||
Returns a list of DateTimeTZRanges representing the days during the specified part of the camp.
|
||||
'''
|
||||
if not hasattr(self, camppart):
|
||||
print("nonexistant field/attribute")
|
||||
return False
|
||||
|
||||
field = getattr(self, camppart)
|
||||
|
||||
if not hasattr(field, '__class__') or not hasattr(field.__class__, '__name__') or not field.__class__.__name__ == 'DateTimeTZRange':
|
||||
print("this attribute is not a datetimetzrange field: %s" % field)
|
||||
return False
|
||||
|
||||
daycount = (field.upper - field.lower).days
|
||||
days = []
|
||||
for i in range(0, daycount):
|
||||
if i == 0:
|
||||
# on the first day use actual start time instead of midnight
|
||||
days.append(
|
||||
DateTimeTZRange(
|
||||
field.lower,
|
||||
(field.lower+timedelta(days=i+1)).replace(hour=0)
|
||||
)
|
||||
)
|
||||
elif i == daycount-1:
|
||||
# on the last day use actual end time instead of midnight
|
||||
days.append(
|
||||
DateTimeTZRange(
|
||||
(field.lower+timedelta(days=i)).replace(hour=0),
|
||||
field.lower+timedelta(days=i+1)
|
||||
)
|
||||
)
|
||||
else:
|
||||
# neither first nor last day, goes from midnight to midnight
|
||||
days.append(
|
||||
DateTimeTZRange(
|
||||
(field.lower+timedelta(days=i)).replace(hour=0),
|
||||
(field.lower+timedelta(days=i+1)).replace(hour=0)
|
||||
)
|
||||
)
|
||||
return days
|
||||
|
||||
@property
|
||||
def buildup_days(self):
|
||||
'''
|
||||
Returns a list of DateTimeTZRanges representing the days during the buildup.
|
||||
'''
|
||||
return self.get_days('buildup')
|
||||
|
||||
@property
|
||||
def camp_days(self):
|
||||
'''
|
||||
Returns a list of DateTimeTZRanges representing the days during the camp.
|
||||
'''
|
||||
return self.get_days('camp')
|
||||
|
||||
@property
|
||||
def teardown_days(self):
|
||||
'''
|
||||
Returns a list of DateTimeTZRanges representing the days during the buildup.
|
||||
'''
|
||||
return self.get_days('teardown')
|
||||
|
||||
|
||||
|
|
61
camps/templates/camp_detail_bornhack-2016.html
Normal file
61
camps/templates/camp_detail_bornhack-2016.html
Normal file
|
@ -0,0 +1,61 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load commonmark %}
|
||||
{% load static from staticfiles %}
|
||||
{% load imageutils %}
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<img src="{% static camp.logo_large %}" class="img-responsive" id="front-logo" />
|
||||
|
||||
<br />
|
||||
|
||||
<div class="col-md-8 text-container">
|
||||
<div class="lead">
|
||||
<strong>Bornhack 2016</strong> was the first BornHack. It took place from <strong>August 27 to September 3rd 2016</strong> on the Danish island of Bornholm. The tagline of this event was <strong>Initial Commit</strong>.
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
{% thumbnail 'img/bornhack-2016/esbjerg' '1600x988-B12A2612.jpg' 'A quiet moment in the chillout area by the bar' %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br />
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
{% thumbnail 'img/bornhack-2016/fonsmark' 'FB1_5128.JPG' 'Emma Holten speaking at BornHack 2016' %}
|
||||
</div>
|
||||
<div class="col-md-8 text-container">
|
||||
<div class="lead">
|
||||
With <strong>3 keynotes, 38 talks, and 9 workshops</strong> we had an ambitious program which was well received by participants. We also had an <strong>amazing list of sponsors</strong> making the event financially possible.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br />
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-8 text-container">
|
||||
<p class="lead">Many of the speakers and keynotes were present for the <strong>full week</strong>, providing participants with <strong>ample opportunity to discuss projects and exchange ideas</strong>.</p>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
{% thumbnail 'img/bornhack-2016/fonsmark' 'FB1_5149.JPG' 'Danish politicians debating at BornHack 2016' %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br />
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
{% thumbnail 'img/bornhack-2016/esbjerg' '1600x988-B12A2631.jpg' 'The BornHack 2016 organiser team' %}
|
||||
</div>
|
||||
<div class="col-md-8 text-container">
|
||||
<div class="lead">
|
||||
The team behind BornHack 2016 had <strong>a lot of help</strong> from volunteer participants when it was needed. We are <strong>very grateful</strong> and will repay the kindness by continuing to organise awesome events.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock content %}
|
||||
|
61
camps/templates/camp_detail_bornhack-2017.html
Normal file
61
camps/templates/camp_detail_bornhack-2017.html
Normal file
|
@ -0,0 +1,61 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load commonmark %}
|
||||
{% load static from staticfiles %}
|
||||
{% load imageutils %}
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<img src="{% static camp.logo_large %}" class="img-responsive" id="front-logo" />
|
||||
|
||||
<br />
|
||||
|
||||
<div class="col-md-8 text-container">
|
||||
<div class="lead">
|
||||
<strong>Bornhack 2017</strong> will be the second BornHack. It will take place from <strong>August 22nd to August 29th 2017</strong> on the Danish island of Bornholm. The tagline of this event will be <strong>Make Tradition</strong>.
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
{% thumbnail 'img/bornhack-2016/esbjerg' '1600x988-B12A2610.jpg' 'The family area at BornHack 2016' %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br />
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
{% thumbnail 'img/bornhack-2016/esbjerg' '1600x988-B12A2631.jpg' 'The BornHack 2016 organiser team' %}
|
||||
</div>
|
||||
<div class="col-md-8 text-container">
|
||||
<div class="lead">
|
||||
The BornHack team looks forward to organising another great event for the hacker community. We <strong>still need volunteers</strong>, so please let us know if you want to help!
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br />
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-8 text-container">
|
||||
<div class="lead">We want to encourage <strong>hackers, makers, politicians, activists, developers, artists, sysadmins, engineers</strong> and anyone else with an interest in <strong>technology and society</strong> to read our <a href="{% url 'call_for_speakers' camp_slug=camp.slug %}">call for speakers</a>.</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
{% thumbnail 'img/bornhack-2016/fonsmark' 'FB1_5149.JPG' 'Danish politicians debating at BornHack 2016' %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br />
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
{% thumbnail 'img/bornhack-2016/fonsmark' 'FB1_5265.JPG' 'Organisers thanking the BornHack 2016 sponsors' %}
|
||||
</div>
|
||||
<div class="col-md-8 text-container">
|
||||
<div class="lead">
|
||||
BornHack aims to <strong>keep ticket prices affordable</strong> for everyone and to that end <strong>we need sponsors</strong>. Please see our <a href="{% url 'sponsors' camp_slug=camp.slug %}">call for sponsors</a> if you want to sponsor us, or if you work for a company you think might be able to help.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock content %}
|
||||
|
43
camps/templates/camp_list.html
Normal file
43
camps/templates/camp_list.html
Normal file
|
@ -0,0 +1,43 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load commonmark %}
|
||||
{% load static from staticfiles %}
|
||||
{% load imageutils %}
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Tagline</th>
|
||||
<th>Buildup</th>
|
||||
<th>Camp</th>
|
||||
<th>Teardown</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for listcamp in camps %}
|
||||
<tr>
|
||||
<td><a href="{% url 'camp_detail' camp_slug=listcamp.slug %}">{{ listcamp.title }}</a></td>
|
||||
<td>{{ listcamp.tagline }}</td>
|
||||
<td>{{ listcamp.buildup.lower }} to {{ listcamp.buildup.upper }}</td>
|
||||
<td>{{ listcamp.camp.lower }} to {{ listcamp.camp.upper }}</td>
|
||||
<td>{{ listcamp.teardown.lower }} to {{ listcamp.teardown.upper }}</td>
|
||||
</tr>
|
||||
</a>
|
||||
{% empty %}
|
||||
<h3>No camps found, what gives?!</h3>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
$("table").on("click", "tr", function(e) {
|
||||
if ($(e.target).is("a,input")) // anything else you don't want to trigger the click
|
||||
return;
|
||||
location.href = $(this).find("a").attr("href");
|
||||
});
|
||||
</script>
|
||||
{% endblock content %}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
<form method="POST">
|
||||
{{ form }}
|
||||
<button type="submit">Submit</button>
|
||||
</form>
|
|
@ -1,3 +0,0 @@
|
|||
{% for camp in camps %}
|
||||
{{ camp }}<br />
|
||||
{% endfor %}
|
|
@ -1,3 +1,17 @@
|
|||
from . import models
|
||||
from django.views.generic import ListView, DetailView
|
||||
from django.utils import timezone
|
||||
from .models import *
|
||||
|
||||
|
||||
class CampDetailView(DetailView):
|
||||
model = Camp
|
||||
slug_url_kwarg = 'camp_slug'
|
||||
|
||||
def get_template_names(self):
|
||||
return 'camp_detail_%s.html' % self.get_object().slug
|
||||
|
||||
|
||||
class CampListView(ListView):
|
||||
model = Camp
|
||||
template_name = 'camp_list.html'
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
Before Width: | Height: | Size: 154 KiB |
6
info/admin.py
Normal file
6
info/admin.py
Normal file
|
@ -0,0 +1,6 @@
|
|||
from django.contrib import admin
|
||||
from .models import *
|
||||
|
||||
admin.site.register(InfoCategory)
|
||||
admin.site.register(InfoItem)
|
||||
|
7
info/apps.py
Normal file
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
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')]),
|
||||
),
|
||||
]
|
29
info/migrations/0002_auto_20161228_2312.py
Normal file
29
info/migrations/0002_auto_20161228_2312.py
Normal file
|
@ -0,0 +1,29 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.4 on 2016-12-28 23:12
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('info', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='infocategory',
|
||||
options={'ordering': ['-weight', 'headline'], 'verbose_name_plural': 'Info Categories'},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='infocategory',
|
||||
name='weight',
|
||||
field=models.PositiveIntegerField(default=100, help_text='Determines sorting/ordering. Heavier categories sink to the bottom. Categories with the same weight are ordered alphabetically. Defaults to 100.'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='infoitem',
|
||||
name='weight',
|
||||
field=models.PositiveIntegerField(default=100, help_text='Determines sorting/ordering. Heavier items sink to the bottom. Items with the same weight are ordered alphabetically. Defaults to 100.'),
|
||||
),
|
||||
]
|
0
info/migrations/__init__.py
Normal file
0
info/migrations/__init__.py
Normal file
80
info/models.py
Normal file
80
info/models.py
Normal file
|
@ -0,0 +1,80 @@
|
|||
from __future__ import unicode_literals
|
||||
from django.contrib import messages
|
||||
from django.db import models
|
||||
from utils.models import CreatedUpdatedModel
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
|
||||
class InfoCategory(CreatedUpdatedModel):
|
||||
class Meta:
|
||||
ordering = ['-weight', 'headline']
|
||||
unique_together = (('anchor', 'camp'), ('headline', 'camp'))
|
||||
verbose_name_plural = "Info Categories"
|
||||
|
||||
camp = models.ForeignKey(
|
||||
'camps.Camp',
|
||||
related_name = 'infocategories',
|
||||
on_delete = models.PROTECT
|
||||
)
|
||||
|
||||
headline = models.CharField(
|
||||
max_length = 100,
|
||||
help_text = "The headline of this info category"
|
||||
)
|
||||
|
||||
anchor = models.SlugField(
|
||||
help_text = "The HTML anchor to use for this info category."
|
||||
)
|
||||
|
||||
weight = models.PositiveIntegerField(
|
||||
help_text = 'Determines sorting/ordering. Heavier categories sink to the bottom. Categories with the same weight are ordered alphabetically. Defaults to 100.',
|
||||
default = 100,
|
||||
)
|
||||
|
||||
def clean(self):
|
||||
if InfoItem.objects.filter(category__camp=self.camp, anchor=self.anchor).exists():
|
||||
# this anchor is already in use on an item, so it cannot be used (must be unique on the page)
|
||||
raise ValidationError({'anchor': 'Anchor is already in use on an info item for this camp'})
|
||||
|
||||
def __str__(self):
|
||||
return '%s (%s)' % (self.headline, self.camp)
|
||||
|
||||
|
||||
class InfoItem(CreatedUpdatedModel):
|
||||
class Meta:
|
||||
ordering = ['-weight', 'headline']
|
||||
unique_together = (('anchor', 'category'), ('headline', 'category'))
|
||||
|
||||
category = models.ForeignKey(
|
||||
'info.InfoCategory',
|
||||
related_name = 'infoitems',
|
||||
on_delete = models.PROTECT
|
||||
)
|
||||
|
||||
headline = models.CharField(
|
||||
max_length = 100,
|
||||
help_text = "Headline of this info item."
|
||||
)
|
||||
|
||||
anchor = models.SlugField(
|
||||
help_text = "The HTML anchor to use for this info item."
|
||||
)
|
||||
|
||||
body = models.TextField(
|
||||
help_text = 'Body of this info item. Markdown is supported.'
|
||||
)
|
||||
|
||||
weight = models.PositiveIntegerField(
|
||||
help_text = 'Determines sorting/ordering. Heavier items sink to the bottom. Items with the same weight are ordered alphabetically. Defaults to 100.',
|
||||
default = 100,
|
||||
)
|
||||
|
||||
def clean(self):
|
||||
if InfoCategory.objects.filter(camp=self.category.camp, anchor=self.anchor).exists():
|
||||
# this anchor is already in use on a category, so it cannot be used here (they must be unique on the entire page)
|
||||
raise ValidationError({'anchor': 'Anchor is already in use on an info category for this camp'})
|
||||
|
||||
def __str__(self):
|
||||
return '%s (%s)' % (self.headline, self.category)
|
||||
|
||||
|
70
info/templates/info.html
Normal file
70
info/templates/info.html
Normal file
|
@ -0,0 +1,70 @@
|
|||
{% 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>
|
||||
|
||||
{% if categories %}
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h3>Table of Contents</h3>
|
||||
<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>{{ category.title }}</h2>
|
||||
<div class="panel-group">
|
||||
{% for item in category.infoitems.all %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<span class="sub-anchor" id="{{ item.anchor }}"></span>
|
||||
<h4 class="panel-title">{{ item.headline }}
|
||||
<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 %}
|
||||
{% else %}
|
||||
<h3>No info found for {{ camp.title }}</h3>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
16
info/views.py
Normal file
16
info/views.py
Normal file
|
@ -0,0 +1,16 @@
|
|||
from django.shortcuts import render
|
||||
from django.views.generic import ListView, DetailView
|
||||
from django.utils import timezone
|
||||
from .models import *
|
||||
from camps.mixins import CampViewMixin
|
||||
|
||||
class CampInfoView(CampViewMixin, ListView):
|
||||
model = InfoCategory
|
||||
template_name = 'info.html'
|
||||
context_object_name = 'categories'
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super(CampInfoView, self).get_queryset()
|
||||
# do not show categories with 0 items
|
||||
return queryset.exclude(infoitems__isnull=True)
|
||||
|
|
@ -5,7 +5,7 @@ import sys
|
|||
if __name__ == "__main__":
|
||||
os.environ.setdefault(
|
||||
"DJANGO_SETTINGS_MODULE",
|
||||
"bornhack.settings.production"
|
||||
"bornhack.settings"
|
||||
)
|
||||
|
||||
from django.core.management import execute_from_command_line
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
||||
|
|
|
@ -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
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
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
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 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()
|
||||
|
|
|
@ -6,6 +6,9 @@ News | {{ block.super }}
|
|||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% if request.resolver_match.kwargs.archived %}
|
||||
<i>Showing archived news items. <a href="{% url 'news:index' archived=False %}">Show regular news items</a></i>
|
||||
{% endif %}
|
||||
{% for item in news_items %}
|
||||
<div>
|
||||
<h3><a href="{% url 'news:detail' slug=item.slug %}">{{ item.title }}</a> <small>{{ item.published_at|date:"Y-m-d" }}</small></h3>
|
||||
|
@ -17,4 +20,9 @@ News | {{ block.super }}
|
|||
{% empty %}
|
||||
<h3>No news yet. Stay tuned!</h3>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
{% if not request.resolver_match.kwargs.archived %}
|
||||
<hr />
|
||||
<i><a href="{% url 'news:archive' archived=True %}">Show archived news items</a></i>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
@ -3,6 +3,8 @@ from . import views
|
|||
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^$', views.NewsIndex.as_view(), name='index'),
|
||||
url(r'^$', views.NewsIndex.as_view(), kwargs={'archived': False}, name='index'),
|
||||
url(r'^archive/$', views.NewsIndex.as_view(), kwargs={'archived': True}, name='archive'),
|
||||
url(r'(?P<slug>[-_\w+]+)/$', views.NewsDetail.as_view(), name='detail'),
|
||||
]
|
||||
|
||||
|
|
|
@ -1,33 +1,22 @@
|
|||
from django.views.generic import ListView, DetailView
|
||||
from django.utils import timezone
|
||||
from . import models
|
||||
|
||||
from .models import *
|
||||
|
||||
class NewsIndex(ListView):
|
||||
model = models.NewsItem
|
||||
model = NewsItem
|
||||
template_name = 'news_index.html'
|
||||
context_object_name = 'news_items'
|
||||
|
||||
def get_queryset(self):
|
||||
return self.model.objects.public()
|
||||
return NewsItem.objects.filter(
|
||||
published_at__isnull=False,
|
||||
published_at__lt=timezone.now(),
|
||||
archived=self.kwargs['archived']
|
||||
)
|
||||
|
||||
|
||||
class NewsDetail(DetailView):
|
||||
model = models.NewsItem
|
||||
model = NewsItem
|
||||
template_name = 'news_detail.html'
|
||||
context_object_name = 'news_item'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(NewsDetail, self).get_context_data(**kwargs)
|
||||
news_item = self.get_object()
|
||||
timed = news_item.published_at > timezone.now()
|
||||
|
||||
if news_item.public and timed:
|
||||
context['not_public'] = True
|
||||
context['timed'] = True
|
||||
elif not news_item.public:
|
||||
context['not_public'] = True
|
||||
context['timed'] = False
|
||||
|
||||
return context
|
||||
|
||||
|
|
|
@ -30,3 +30,4 @@ class Profile(CreatedUpdatedModel, UUIDModel):
|
|||
def create_profile(sender, created, instance, **kwargs):
|
||||
if created:
|
||||
Profile.objects.create(user=instance)
|
||||
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
default_app_config = 'program.apps.ProgramConfig'
|
||||
|
|
@ -1,8 +1,12 @@
|
|||
from django.contrib import admin
|
||||
|
||||
from .models import Event, Speaker, EventType
|
||||
from .models import Event, Speaker, EventType, EventInstance
|
||||
|
||||
|
||||
@admin.register(EventInstance)
|
||||
class EventInstanceAdmin(admin.ModelAdmin):
|
||||
pass
|
||||
|
||||
@admin.register(EventType)
|
||||
class EventTypeAdmin(admin.ModelAdmin):
|
||||
pass
|
||||
|
@ -22,17 +26,8 @@ class EventAdmin(admin.ModelAdmin):
|
|||
list_display = [
|
||||
'title',
|
||||
'event_type',
|
||||
'get_days',
|
||||
'start',
|
||||
'end',
|
||||
]
|
||||
|
||||
def get_days(self, obj):
|
||||
return ', '.join([
|
||||
str(day.date.strftime('%a'))
|
||||
for day in obj.days.all()
|
||||
])
|
||||
|
||||
inlines = [
|
||||
SpeakerInline
|
||||
]
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
from django.db.models.signals import m2m_changed, pre_save
|
||||
from .signal_handlers import check_speaker_event_camp_consistency, check_speaker_camp_change
|
||||
|
||||
class ProgramConfig(AppConfig):
|
||||
name = 'program'
|
||||
|
||||
def ready(self):
|
||||
from .models import Speaker
|
||||
m2m_changed.connect(check_speaker_event_camp_consistency, sender=Speaker.events.through)
|
||||
pre_save.connect(check_speaker_camp_change, sender=Speaker)
|
||||
|
||||
|
|
52
program/migrations/0010_auto_20161212_1809.py
Normal file
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'),
|
||||
),
|
||||
]
|
33
program/migrations/0011_auto_20161229_2149.py
Normal file
33
program/migrations/0011_auto_20161229_2149.py
Normal file
|
@ -0,0 +1,33 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.4 on 2016-12-29 21:49
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import django.contrib.postgres.fields.ranges
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('program', '0010_auto_20161212_1809'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='eventinstance',
|
||||
options={'ordering': ['when']},
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='eventinstance',
|
||||
name='end',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='eventinstance',
|
||||
name='start',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='eventinstance',
|
||||
name='when',
|
||||
field=django.contrib.postgres.fields.ranges.DateTimeRangeField(null=True),
|
||||
),
|
||||
]
|
21
program/migrations/0012_auto_20161229_2150.py
Normal file
21
program/migrations/0012_auto_20161229_2150.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.4 on 2016-12-29 21:50
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import django.contrib.postgres.fields.ranges
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('program', '0011_auto_20161229_2149'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='eventinstance',
|
||||
name='when',
|
||||
field=django.contrib.postgres.fields.ranges.DateTimeRangeField(),
|
||||
),
|
||||
]
|
21
program/migrations/0013_auto_20170121_1312.py
Normal file
21
program/migrations/0013_auto_20170121_1312.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.5 on 2017-01-21 12:12
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('program', '0012_auto_20161229_2150'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='event',
|
||||
name='camp',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='events', to='camps.Camp'),
|
||||
),
|
||||
]
|
22
program/migrations/0014_speaker_camp.py
Normal file
22
program/migrations/0014_speaker_camp.py
Normal file
|
@ -0,0 +1,22 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.5 on 2017-01-22 13:39
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('camps', '0017_remove_camp_description'),
|
||||
('program', '0013_auto_20170121_1312'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='speaker',
|
||||
name='camp',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='speakers', to='camps.Camp'),
|
||||
),
|
||||
]
|
|
@ -1,9 +1,11 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
from django.contrib.postgres.fields import DateTimeRangeField
|
||||
from django.db import models
|
||||
from django.utils.text import slugify
|
||||
|
||||
from django.conf import settings
|
||||
from utils.models import CreatedUpdatedModel
|
||||
from django.core.exceptions import ValidationError
|
||||
from datetime import timedelta
|
||||
|
||||
|
||||
class EventType(CreatedUpdatedModel):
|
||||
|
@ -18,33 +20,83 @@ class EventType(CreatedUpdatedModel):
|
|||
|
||||
|
||||
class Event(CreatedUpdatedModel):
|
||||
""" Something that is on the program. """
|
||||
""" Something that is on the program one or more times. """
|
||||
title = models.CharField(max_length=255)
|
||||
slug = models.SlugField(blank=True, max_length=255)
|
||||
abstract = models.TextField()
|
||||
event_type = models.ForeignKey(EventType)
|
||||
days = models.ManyToManyField('camps.Day', blank=True)
|
||||
start = models.TimeField(null=True, blank=True)
|
||||
end = models.TimeField(null=True, blank=True)
|
||||
camp = models.ForeignKey('camps.Camp', null=True, related_name="events")
|
||||
|
||||
class Meta:
|
||||
ordering = ['title']
|
||||
|
||||
def __unicode__(self):
|
||||
return self.title
|
||||
return '%s (%s)' % (self.title, self.camp.title)
|
||||
|
||||
def save(self, **kwargs):
|
||||
if not self.slug:
|
||||
self.slug = slugify(self.title)
|
||||
super(Event, self).save(**kwargs)
|
||||
|
||||
@property
|
||||
def speakers_list(self):
|
||||
if self.speakers.exists():
|
||||
return ", ".join(self.speakers.all().values_list('name', flat=True))
|
||||
return False
|
||||
|
||||
|
||||
class EventInstance(CreatedUpdatedModel):
|
||||
""" An instance of an event """
|
||||
event = models.ForeignKey('program.event', related_name='instances')
|
||||
when = DateTimeRangeField()
|
||||
|
||||
class Meta:
|
||||
ordering = ['when']
|
||||
|
||||
def __unicode__(self):
|
||||
return '%s (%s)' % (self.event, self.when)
|
||||
|
||||
def __clean__(self):
|
||||
errors = []
|
||||
if self.when.lower > self.when.upper:
|
||||
errors.append(ValidationError({'when', "Start should be earlier than finish"}))
|
||||
|
||||
if self.when.lower.time().minute != 0 and self.when.lower.time().minute != 30:
|
||||
errors.append(ValidationError({'when', "Start time minute should be 0 or 30."}))
|
||||
|
||||
if self.when.upper.time().minute != 0 and self.when.upper.time().minute != 30:
|
||||
errors.append(ValidationError({'when', "End time minute should be 0 or 30."}))
|
||||
|
||||
if errors:
|
||||
raise ValidationError(errors)
|
||||
|
||||
@property
|
||||
def schedule_date(self):
|
||||
"""
|
||||
Returns the schedule date of this eventinstance. Schedule date is determined by substracting
|
||||
settings.SCHEDULE_MIDNIGHT_OFFSET_HOURS from the eventinstance start time. This means that if
|
||||
an event is scheduled for 00:30 wednesday evening (technically thursday) then the date
|
||||
after substracting 5 hours would be wednesdays date, not thursdays.
|
||||
"""
|
||||
return (self.when.lower-timedelta(hours=settings.SCHEDULE_MIDNIGHT_OFFSET_HOURS)).date()
|
||||
|
||||
@property
|
||||
def timeslots(self):
|
||||
"""
|
||||
Find the number of timeslots this eventinstance takes up
|
||||
"""
|
||||
seconds = (self.when.upper-self.when.lower).seconds
|
||||
minutes = seconds / 60
|
||||
return minutes / settings.SCHEDULE_TIMESLOT_LENGTH_MINUTES
|
||||
|
||||
|
||||
class Speaker(CreatedUpdatedModel):
|
||||
""" Person anchoring an event. """
|
||||
""" A Person anchoring an event. """
|
||||
name = models.CharField(max_length=150)
|
||||
biography = models.TextField()
|
||||
picture = models.ImageField(null=True, blank=True)
|
||||
slug = models.SlugField(blank=True, max_length=255)
|
||||
camp = models.ForeignKey('camps.Camp', null=True, related_name="speakers")
|
||||
events = models.ManyToManyField(
|
||||
Event,
|
||||
related_name='speakers',
|
||||
|
@ -56,7 +108,7 @@ class Speaker(CreatedUpdatedModel):
|
|||
ordering = ['name']
|
||||
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
return '%s (%s)' % (self.name, self.camp)
|
||||
|
||||
def save(self, **kwargs):
|
||||
if not self.slug:
|
||||
|
|
16
program/signal_handlers.py
Normal file
16
program/signal_handlers.py
Normal file
|
@ -0,0 +1,16 @@
|
|||
from django.core.exceptions import ValidationError
|
||||
|
||||
def check_speaker_event_camp_consistency(sender, instance, **kwargs):
|
||||
if kwargs['action'] == 'pre_add':
|
||||
for pk in kwargs['pk_set']:
|
||||
# check if this event belongs to a different event than the speaker does
|
||||
from program.models import Event
|
||||
event = Event.objects.get(id=pk)
|
||||
if event.camp != instance.camp:
|
||||
raise ValidationError({'events': 'One or more events belong to a different camp (%s) than the speaker (%s) does' % (event.camp, instance.camp)})
|
||||
|
||||
def check_speaker_camp_change(sender, instance, **kwargs):
|
||||
for event in instance.events.all():
|
||||
if event.camp != instance.camp:
|
||||
raise ValidationError({'camp': 'You cannot change the camp a speaker belongs to if the speaker is associated with one or more events.'})
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
<div class="list-group">
|
||||
{% for event in event_list %}
|
||||
{% if event.event_type.name != "Facilities" %}
|
||||
<a href="{% url 'schedule:event' slug=event.slug %}" class="list-group-item">
|
||||
<a href="{% url 'event_detail' camp_slug=camp.slug slug=event.slug %}" class="list-group-item">
|
||||
<small style="background-color: {{ event.event_type.color }}; border: 0; color: {% if event.event_type.light_text %}white{% else %}black{% endif %}; display: inline-block; padding: 5px;">
|
||||
{{ event.event_type.name }}
|
||||
</small>
|
||||
|
|
|
@ -1,19 +1,49 @@
|
|||
{% extends 'schedule_base.html' %}
|
||||
|
||||
{% block schedule_content %}
|
||||
<div class="row">
|
||||
<div class="btn-group btn-group-justified">
|
||||
<a href="{% url 'schedule_index' camp_slug=camp.slug %}{% if eventtype %}?type={{ eventtype.slug }}{% endif %}" class="btn {% if urlyear %}btn-default{% else %}btn-primary{% endif %}">Overview</a>
|
||||
{% for day in camp.camp_days %}
|
||||
{% with day.lower.date|date:"m" as month_padded %}
|
||||
{% with day.lower.date|date:"d" as day_padded %}
|
||||
<a href="{% url 'schedule_day' camp_slug=camp.slug year=day.lower.date.year month=month_padded day=day_padded %}{% if eventtype %}?type={{ eventtype.slug }}{% endif %}" class="btn btn-sm {% if urlyear and urlyear|add:"0" == day.lower.date.year and urlmonth == month_padded and urlday == day_padded %}btn-primary{% else %}btn-default{% endif %}">
|
||||
{{ day.lower.date|date:"l" }}
|
||||
</a>
|
||||
{% endwith %}
|
||||
{% endwith %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a href="{% url 'schedule:index' %}" class="btn btn-default" style="display: inline-block; padding: 5px;">
|
||||
Overview
|
||||
</a>
|
||||
{% for day in days %}
|
||||
{% with day.date|date:"m" as month_padded %}
|
||||
{% with day.date|date:"d" as day_padded %}
|
||||
<a href="{% url 'schedule:day' year=day.date.year month=month_padded day=day_padded %}" class="btn btn-default" style="display: inline-block; padding: 5px;">
|
||||
{{ day.date|date:"l" }}
|
||||
</a>
|
||||
{% endwith %}
|
||||
{% endwith %}
|
||||
{% endfor %}
|
||||
<p>
|
||||
|
||||
<div class="row">
|
||||
<div class="btn-group btn-group-justified">
|
||||
|
||||
{% if not urlyear %}
|
||||
<a href="{% url 'schedule_index' camp_slug=camp.slug %}" style="background-color: black; border: 0; color: white;" class="btn">
|
||||
{% if eventtype %}All{% else %}<b>All</b>{% endif %}
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="{% url 'schedule_day' camp_slug=camp.slug year=urlyear month=urlmonth day=urlday %}" style="background-color: black; border: 0; color: white;" class="btn">
|
||||
{% if eventtype %}All{% else %}<b>All</b>{% endif %}
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{% for event_type in camp.event_types %}
|
||||
{% if not urlyear %}
|
||||
<a href="{% url 'schedule_index' camp_slug=camp.slug %}?type={{ event_type.slug }}" style="background-color: {{ event_type.color }}; border: 0; color: {% if event_type.light_text %}white{% else %}black{% endif %};" class="btn">
|
||||
{% if eventtype and eventtype == event_type %}<b>{{ event_type.name }}</b>{% else %}{{ event_type.name }}{% endif %}
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="{% url 'schedule_day' camp_slug=camp.slug year=urlyear month=urlmonth day=urlday %}?type={{ event_type.slug }}" style="background-color: {{ event_type.color }}; border: 0; color: {% if event_type.light_text %}white{% else %}black{% endif %};" class="btn">
|
||||
{% if eventtype and eventtype == event_type %}<b>{{ event_type.name }}</b>{% else %}{{ event_type.name }}{% endif %}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
|
|
|
@ -3,17 +3,14 @@
|
|||
{% block program_content %}
|
||||
<h2>{{ date|date:"l, F jS" }}</h2>
|
||||
{% for event in events %}
|
||||
|
||||
{% ifchanged event.event_type %}
|
||||
|
||||
{% if not forloop.first %}</div>{% endif %}
|
||||
<h3>{{ event.event_type }}</h3>
|
||||
<div style="display: flex; flex-wrap: wrap;">
|
||||
|
||||
{% endifchanged %}
|
||||
|
||||
<a class="event"
|
||||
href="{% url 'schedule:event' slug=event.slug %}"
|
||||
href="{% url 'event_detail' camp_slut=camp.slug slug=event.slug %}"
|
||||
style="background-color: {{ event.event_type.color }}; border: 0; color: {% if event.event_type.light_text %}white{% else %}black{% endif %};">
|
||||
<small>{{ event.start|date:"H:i" }} - {{ event.end|date:"H:i" }}</small>
|
||||
<br />
|
||||
|
@ -25,6 +22,27 @@
|
|||
</a>
|
||||
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<tbody>
|
||||
<table>
|
||||
{% for timeslot in timeslots %}
|
||||
<tr>
|
||||
<td style="height: 50px; padding: 5px;">{{ timeslot.time }}</td>
|
||||
{% for eventinstance in eventinstances %}
|
||||
{% if eventinstance.when.lower.time == timeslot.time %}
|
||||
<td style="background-color: {{ eventinstance.event.event_type.color }}; color: {% if event.event_type.light_text %}white{% else %}black{% endif %};" class="event" rowspan={{ eventinstance.timeslots }}>
|
||||
<a style="color:inherit;" href="{% url 'event_detail' camp_slug=camp.slug slug=eventinstance.event.slug %}">
|
||||
{{ eventinstance.event.title }}<br>
|
||||
{{ eventinstance.when.lower.time }}-{{ eventinstance.when.upper.time }}
|
||||
</a>
|
||||
</td>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{% endblock program_content %}
|
||||
|
|
|
@ -2,35 +2,32 @@
|
|||
{% load commonmark %}
|
||||
|
||||
{% block schedule_content %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading" ><span style="font-size: x-large"><span style="background-color: {{ event.event_type.color }}; border: 0; color: {% if event.event_type.light_text %}white{% else %}black{% endif %}; display: inline-block; padding: 5px;">{{ event.event_type.name }}</span> {{ event.title }}</span></div>
|
||||
<div class="panel-body">
|
||||
<p>
|
||||
{{ event.abstract|commonmark }}
|
||||
{% if event.speakers.exists %}
|
||||
<hr>
|
||||
{% for speaker in event.speakers.all %}
|
||||
<h4><a href="{% url 'speaker_detail' camp_slug=camp.slug slug=speaker.slug %}">{{ speaker }}</a></h4>
|
||||
{{ speaker.biography|commonmark }}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</p>
|
||||
|
||||
<h3>
|
||||
<small style="background-color: {{ event.event_type.color }}; border: 0; color: {% if event.event_type.light_text %}white{% else %}black{% endif %}; display: inline-block; padding: 5px;">
|
||||
{{ event.event_type.name }}
|
||||
</small>
|
||||
{{ event.title }}
|
||||
</h3>
|
||||
|
||||
<h4>
|
||||
{% if event.start and event.end and event.days.all.exists %}
|
||||
{{ event.start|date:"H:i" }} - {{ event.end|date:"H:i" }} on
|
||||
{% for day in event.days.all %}{{ day.date|date:"l" }}{% if not forloop.last %}, {% endif %}{% endfor %}<br />
|
||||
{% else %}
|
||||
Not scheduled yet
|
||||
{% endif %}
|
||||
</h4>
|
||||
|
||||
{{ event.abstract|commonmark }}
|
||||
|
||||
<hr />
|
||||
|
||||
{% if event.speakers.exists %}
|
||||
{% for speaker in event.speakers.all %}
|
||||
|
||||
<h3><a href="{% url 'schedule:speaker_detail' slug=speaker.slug %}">{{ speaker }}</a></h3>
|
||||
{{ speaker.biography|commonmark }}
|
||||
|
||||
{% endfor %}
|
||||
|
||||
{% endif %}
|
||||
<hr>
|
||||
|
||||
<h4>Instances</h4>
|
||||
<ul class="list-group">
|
||||
{% for ei in event.instances.all %}
|
||||
<li class="list-group-item">{{ ei.when.lower|date:"l M. d H:i" }} - {{ ei.when.upper|date:"H:i" }}</li>
|
||||
{% empty %}
|
||||
No instances scheduled yet
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock schedule_content %}
|
||||
|
||||
|
|
|
@ -1,36 +1,28 @@
|
|||
{% extends 'program_base.html' %}
|
||||
|
||||
{% block program_content %}
|
||||
<a href="{% url 'schedule:index' %}" style="background-color: black; border: 0; color: white; display: inline-block; padding: 5px;">
|
||||
All
|
||||
</a>
|
||||
{% for event_type in event_types %}
|
||||
<a href="{% url 'schedule:index' %}?type={{ event_type.slug }}" style="background-color: {{ event_type.color }}; border: 0; color: {% if event_type.light_text %}white{% else %}black{% endif %}; display: inline-block; padding: 5px;">
|
||||
{{ event_type.name }}
|
||||
</a>
|
||||
{% endfor %}
|
||||
|
||||
<hr />
|
||||
|
||||
{% for day, events in day_events.items %}
|
||||
{{ day.date|date:"D d/m" }} <br />
|
||||
{% for day in camp.camp_days %}
|
||||
{{ day.lower.date|date:"D d/m" }} <br />
|
||||
<div style="display: flex; flex-wrap: wrap;">
|
||||
{% for event in events %}
|
||||
{% for event in camp.events.all %}
|
||||
{% for eventinstance in event.instances.all %}
|
||||
{% if eventinstance.schedule_date == day.lower.date %}
|
||||
{% if not eventtype or eventtype == eventinstance.event.event_type %}
|
||||
<a class="event"
|
||||
href="{% url 'schedule:event' slug=event.slug %}"
|
||||
style="background-color: {{ event.event_type.color }}; border: 0; color: {% if event.event_type.light_text %}white{% else %}black{% endif %};">
|
||||
<small>{{ event.start|date:"H:i" }} - {{ event.end|date:"H:i" }}</small>
|
||||
href="{% url 'event_detail' camp_slug=camp.slug slug=eventinstance.event.slug %}"
|
||||
style="background-color: {{ eventinstance.event.event_type.color }}; border: 0; color: {% if eveninstance.event.event_type.light_text %}white{% else %}black{% endif %};">
|
||||
<small>{{ eventinstance.when.lower|date:"H:i" }} - {{ eventinstance.when.upper|date:"H:i" }}</small>
|
||||
<br />
|
||||
{{ event }}
|
||||
<br />
|
||||
{% if event.speakers.exists %}
|
||||
<i>by {{ event.speakers.all|join:", " }}
|
||||
{% endif %}</i>
|
||||
|
||||
{% if event.speakers.exists %}<i>by {{ event.speakers_list }}{% endif %}</i>
|
||||
</a>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
<hr />
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
|
||||
{% endblock program_content %}
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="row">
|
||||
<div class="btn-group btn-group-justified">
|
||||
<a href="{% url 'schedule_index' camp_slug=camp.slug %}" class="btn {% if request.resolver_match.url_name == "schedule_index" or urlyear %}btn-primary{% else %}btn-default{% endif %}">Schedule</a>
|
||||
<a href="{% url 'call_for_speakers' camp_slug=camp.slug %}" class="btn {% if request.resolver_match.url_name == "call_for_speakers" %}btn-primary{% else %}btn-default{% endif %}">Call for Speakers</a>
|
||||
<a href="{% url 'speaker_index' camp_slug=camp.slug %}" class="btn {% if request.resolver_match.url_name == "speaker_index" %}btn-primary{% else %}btn-default{% endif %}">Speakers</a>
|
||||
<a href="{% url 'event_index' camp_slug=camp.slug %}" class="btn {% if request.resolver_match.url_name == "event_index" %}btn-primary{% else %}btn-default{% endif %}">Talks & Events</a>
|
||||
</div>
|
||||
</div>
|
||||
<p>
|
||||
<a href="{% url 'schedule:index' %}" class="btn btn-default">Schedule</a>
|
||||
<a href="{% url 'call-for-speakers' %}" class="btn btn-default">Call for Speakers</a>
|
||||
<a href="{% url 'schedule:speaker_index' %}" class="btn btn-default">Speakers</a>
|
||||
<a href="{% url 'schedule:event_index' %}" class="btn btn-default">Talks & Events</a>
|
||||
</p>
|
||||
|
||||
<hr />
|
||||
|
||||
{% block schedule_content %}
|
||||
{% endblock schedule_content %}
|
||||
|
||||
|
|
|
@ -9,23 +9,27 @@
|
|||
<hr />
|
||||
|
||||
{% if speaker.events.exists %}
|
||||
{% for event in speaker.events.all %}
|
||||
{% for event in speaker.events.all %}
|
||||
<h3>
|
||||
<small style="background-color: {{ event.event_type.color }}; border: 0; color: {% if event.event_type.light_text %}white{% else %}black{% endif %}; display: inline-block; padding: 5px;">
|
||||
{{ event.event_type.name }}
|
||||
</small>
|
||||
<a href="{% url 'event_detail' camp_slug=camp.slug slug=event.slug %}">{{ event.title }}</a>
|
||||
</h3>
|
||||
{{ event.abstract|commonmark }}
|
||||
|
||||
<h3>
|
||||
<small style="background-color: {{ event.event_type.color }}; border: 0; color: {% if event.event_type.light_text %}white{% else %}black{% endif %}; display: inline-block; padding: 5px;">
|
||||
{{ event.event_type.name }}
|
||||
</small><br>
|
||||
<a href="{% url 'schedule:event' slug=event.slug %}">{{ event.title }}</a></h3>
|
||||
{{ event.abstract|commonmark }}
|
||||
{% if event.start and event.end and event.days.all.exists %}
|
||||
At {{ event.start|date:"H:i" }} - {{ event.end|date:"H:i" }} on
|
||||
{% for day in event.days.all %}{{ day.date|date:"l" }}{% if not forloop.last %}, {% endif %}{% endfor %}<br />
|
||||
{% else %}
|
||||
Not scheduled yet
|
||||
<h4>Instances</h4>
|
||||
<ul class="list-group">
|
||||
{% for ei in event.instances.all %}
|
||||
<li class="list-group-item">{{ ei.when.lower|date:"l M. d H:i" }} - {{ ei.when.upper|date:"H:i" }}</li>
|
||||
{% empty %}
|
||||
No instances scheduled yet
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</h4>
|
||||
<hr>
|
||||
{% empty %}
|
||||
No events registered for this speaker yet
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% endfor %}
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% endblock schedule_content %}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
<div class="list-group">
|
||||
{% for speaker in speaker_list %}
|
||||
<a href="{% url 'schedule:speaker_detail' slug=speaker.slug %}" class="list-group-item">
|
||||
<a href="{% url 'speaker_detail' camp_slug=camp.slug slug=speaker.slug %}" class="list-group-item">
|
||||
{{ speaker.name }} ({{ speaker.events.all.count }} event{{ speaker.events.all.count|pluralize }})
|
||||
</a>
|
||||
{% endfor %}
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
from django.conf.urls import url
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})/$', views.ProgramDayView.as_view(), name='day'),
|
||||
url(r'^$', views.ProgramOverviewView.as_view(), name='index'),
|
||||
url(r'^speakers/$', views.SpeakerListView.as_view(), name='speaker_index'),
|
||||
url(r'^speakers/(?P<slug>[-_\w+]+)/$', views.SpeakerDetailView.as_view(), name='speaker_detail'),
|
||||
url(r'^events/$', views.EventListView.as_view(), name='event_index'),
|
||||
url(r'^(?P<slug>[-_\w+]+)/$', views.EventDetailView.as_view(), name='event'),
|
||||
]
|
127
program/views.py
127
program/views.py
|
@ -1,82 +1,111 @@
|
|||
from collections import OrderedDict
|
||||
|
||||
import datetime
|
||||
from django.views.generic import ListView, TemplateView, DetailView
|
||||
|
||||
from camps.models import Day
|
||||
from camps.mixins import CampViewMixin
|
||||
from . import models
|
||||
from django.http import Http404
|
||||
import datetime
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
class SpeakerDetailView(DetailView):
|
||||
class SpeakerDetailView(CampViewMixin, DetailView):
|
||||
model = models.Speaker
|
||||
template_name = 'speaker_detail.html'
|
||||
|
||||
class SpeakerListView(ListView):
|
||||
|
||||
class SpeakerListView(CampViewMixin, ListView):
|
||||
model = models.Speaker
|
||||
template_name = 'speaker_list.html'
|
||||
|
||||
class EventListView(ListView):
|
||||
def get_queryset(self, *args, **kwargs):
|
||||
return models.Speaker.objects.filter(camp=self.camp)
|
||||
|
||||
|
||||
class EventListView(CampViewMixin, ListView):
|
||||
model = models.Event
|
||||
template_name = 'event_list.html'
|
||||
|
||||
class ProgramOverviewView(ListView):
|
||||
|
||||
class ProgramOverviewView(CampViewMixin, ListView):
|
||||
model = models.Event
|
||||
template_name = 'program_overview.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(
|
||||
ProgramOverviewView, self
|
||||
).get_context_data(**kwargs)
|
||||
|
||||
days = Day.objects.all()
|
||||
context['days'] = days
|
||||
|
||||
filter = {}
|
||||
def dispatch(self, *args, **kwargs):
|
||||
""" If an event type has been supplied check if it is valid """
|
||||
if 'type' in self.request.GET:
|
||||
event_type = self.request.GET['type']
|
||||
filter["event_type__slug"] = event_type
|
||||
|
||||
context['day_events'] = OrderedDict([
|
||||
(
|
||||
day,
|
||||
self.get_queryset().filter(
|
||||
days__in=[day],
|
||||
**filter
|
||||
).order_by(
|
||||
'start'
|
||||
try:
|
||||
eventtype = models.EventType.objects.get(
|
||||
slug=self.request.GET['type']
|
||||
)
|
||||
)
|
||||
for day in days
|
||||
])
|
||||
|
||||
context['event_types'] = models.EventType.objects.all()
|
||||
except models.EventType.DoesNotExist:
|
||||
raise Http404
|
||||
return super(ProgramOverviewView, self).dispatch(*args, **kwargs)
|
||||
|
||||
def get_context_data(self, *args, **kwargs):
|
||||
context = super(ProgramOverviewView, self).get_context_data(**kwargs)
|
||||
if 'type' in self.request.GET:
|
||||
context['eventtype'] = models.EventType.objects.get(slug=self.request.GET['type'])
|
||||
return context
|
||||
|
||||
|
||||
class ProgramDayView(TemplateView):
|
||||
class ProgramDayView(CampViewMixin, TemplateView):
|
||||
template_name = 'program_day.html'
|
||||
def dispatch(self, *args, **kwargs):
|
||||
""" If an event type has been supplied check if it is valid """
|
||||
if 'type' in self.request.GET:
|
||||
try:
|
||||
eventtype = models.EventType.objects.get(
|
||||
slug=self.request.GET['type']
|
||||
)
|
||||
except models.EventType.DoesNotExist:
|
||||
raise Http404
|
||||
return super(ProgramDayView, self).dispatch(*args, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
def get_context_data(self, *args, **kwargs):
|
||||
context = super(ProgramDayView, self).get_context_data(**kwargs)
|
||||
year = int(kwargs['year'])
|
||||
month = int(kwargs['month'])
|
||||
day = int(kwargs['day'])
|
||||
date = datetime.date(year=year, month=month, day=day)
|
||||
day = Day.objects.filter(date=date)
|
||||
context['date'] = date
|
||||
context['events'] = models.Event.objects.filter(days=day).order_by('event_type', 'start')
|
||||
context['event_types'] = models.EventType.objects.all()
|
||||
context['days'] = Day.objects.filter(date__year=year)
|
||||
when = datetime.datetime(year=int(self.kwargs['year']), month=int(self.kwargs['month']), day=int(self.kwargs['day']))
|
||||
eventinstances = models.EventInstance.objects.filter(event__in=self.camp.events.all())
|
||||
skip = []
|
||||
for ei in eventinstances:
|
||||
if ei.schedule_date != when.date():
|
||||
skip.append(ei.id)
|
||||
else:
|
||||
if 'type' in self.request.GET:
|
||||
eventtype = models.EventType.objects.get(
|
||||
slug=self.request.GET['type']
|
||||
)
|
||||
if ei.event.event_type != eventtype:
|
||||
skip.append(ei.id)
|
||||
context['eventinstances'] = eventinstances.exclude(id__in=skip).order_by('event__event_type')
|
||||
|
||||
start = when + datetime.timedelta(hours=settings.SCHEDULE_MIDNIGHT_OFFSET_HOURS)
|
||||
timeslots = []
|
||||
# calculate how many timeslots we have in the schedule based on the lenght of the timeslots in minutes,
|
||||
# and the number of minutes in 24 hours
|
||||
for i in range(0,(24*60)/settings.SCHEDULE_TIMESLOT_LENGTH_MINUTES):
|
||||
timeslot = start + datetime.timedelta(minutes=i*settings.SCHEDULE_TIMESLOT_LENGTH_MINUTES)
|
||||
timeslots.append(timeslot)
|
||||
context['timeslots'] = timeslots
|
||||
|
||||
# include the components to make the urls
|
||||
context['urlyear'] = self.kwargs['year']
|
||||
context['urlmonth'] = self.kwargs['month']
|
||||
context['urlday'] = self.kwargs['day']
|
||||
|
||||
if 'type' in self.request.GET:
|
||||
context['eventtype'] = models.EventType.objects.get(slug=self.request.GET['type'])
|
||||
|
||||
return context
|
||||
|
||||
|
||||
class EventDetailView(DetailView):
|
||||
|
||||
class EventDetailView(CampViewMixin, DetailView):
|
||||
model = models.Event
|
||||
template_name = 'program_event_detail.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(EventDetailView, self).get_context_data(**kwargs)
|
||||
# TODO: date__year is hardcoded here - need fix for 2017 :P
|
||||
context['days'] = Day.objects.filter(date__year=2016)
|
||||
return context
|
||||
|
||||
class CallForSpeakersView(CampViewMixin, TemplateView):
|
||||
def get_template_names(self):
|
||||
return 'call_for_speakers_%s.html' % self.camp.slug
|
||||
|
||||
|
||||
|
|
27
requirements.txt
Normal file
27
requirements.txt
Normal file
|
@ -0,0 +1,27 @@
|
|||
CommonMark==0.7.3
|
||||
Django==1.10.5
|
||||
Pillow==4.0.0
|
||||
PyPDF2==1.26.0
|
||||
Unidecode==0.04.20
|
||||
argparse==1.2.1
|
||||
bleach==1.5.0
|
||||
django-allauth==0.30.0
|
||||
django-bleach==0.3.0
|
||||
django-bootstrap3==8.1.0
|
||||
django-debug-toolbar==1.6
|
||||
django-environ==0.4.1
|
||||
django-wkhtmltopdf==3.1.0
|
||||
future==0.16.0
|
||||
html5lib==0.9999999
|
||||
oauthlib==2.0.1
|
||||
olefile==0.44
|
||||
psycopg2==2.6.2
|
||||
python-openid==2.2.5
|
||||
pytz==2016.10
|
||||
qrcode==5.3
|
||||
requests==2.12.5
|
||||
requests-oauthlib==0.7.0
|
||||
six==1.10.0
|
||||
sqlparse==0.2.2
|
||||
webencodings==0.5
|
||||
wsgiref==0.1.2
|
|
@ -1,12 +0,0 @@
|
|||
django>=1.9
|
||||
django-allauth>=0.23.0
|
||||
django-bootstrap3>=7.0.0
|
||||
django-environ>=0.4.0
|
||||
psycopg2>=2.6.1
|
||||
PyPDF2>=1.25.1
|
||||
django-wkhtmltopdf>=3.0.0
|
||||
Pillow==3.2.0
|
||||
qrcode==5.3
|
||||
CommonMark==0.6.4
|
||||
django-bleach==0.3.0
|
||||
Unidecode==0.4.19
|
|
@ -1,2 +0,0 @@
|
|||
-r base.txt
|
||||
django-debug-toolbar==1.3.2
|
|
@ -1 +0,0 @@
|
|||
-r base.txt
|
|
@ -58,7 +58,6 @@ class OrderAdmin(admin.ModelAdmin):
|
|||
]
|
||||
|
||||
list_filter = [
|
||||
'camp',
|
||||
'payment_method',
|
||||
'open',
|
||||
'paid',
|
||||
|
|
|
@ -20,3 +20,4 @@ def user_has_tickets(request):
|
|||
).exists():
|
||||
has_tickets = True
|
||||
return {'has_tickets': has_tickets}
|
||||
|
||||
|
|
23
shop/migrations/0033_auto_20161212_1756.py
Normal file
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):
|
||||
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}))
|
||||
|
|
|
@ -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
|
||||
|
|
0
sponsors/__init__.py
Normal file
0
sponsors/__init__.py
Normal file
3
sponsors/admin.py
Normal file
3
sponsors/admin.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
7
sponsors/apps.py
Normal file
7
sponsors/apps.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class SponsorsConfig(AppConfig):
|
||||
name = 'sponsors'
|
0
sponsors/migrations/__init__.py
Normal file
0
sponsors/migrations/__init__.py
Normal file
5
sponsors/models.py
Normal file
5
sponsors/models.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
|
@ -6,11 +6,10 @@ Call for Sponsors | {{ block.super }}
|
|||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Our Sponsors</h2>
|
||||
<h2>BornHack 2016 Sponsors</h2>
|
||||
<p class="lead">
|
||||
This is an alphabetical list of our current sponsors. We are immensely
|
||||
grateful for all the help we are getting, and we are looking forward to
|
||||
adding more names to this list.
|
||||
This is an alphabetical list of the BornHack 2016 sponsors. An event like BornHack can not be built on hard work and
|
||||
good intentions alone - it would simply not have been possible without the financial help from these organisations. Thank you, we are immensely grateful!
|
||||
</p>
|
||||
<div class="text-center">
|
||||
<hr>
|
||||
|
@ -116,7 +115,7 @@ Call for Sponsors | {{ block.super }}
|
|||
<a href="mailto:thomas@tyktech.dk">
|
||||
<img src="{% static 'img/sponsors/tyktech_logo.png' %}" class="img-responsive center-block" alt="tyktech logo">
|
||||
</a>
|
||||
Badges, lanyards, wristbands
|
||||
Badges, wristbands
|
||||
</p>
|
||||
<hr>
|
||||
<p class="lead">
|
3
sponsors/tests.py
Normal file
3
sponsors/tests.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
9
sponsors/views.py
Normal file
9
sponsors/views.py
Normal file
|
@ -0,0 +1,9 @@
|
|||
from django.views.generic import TemplateView
|
||||
from camps.mixins import CampViewMixin
|
||||
|
||||
|
||||
class SponsorView(CampViewMixin, TemplateView):
|
||||
def get_template_names(self):
|
||||
return '%s-sponsors.html' % self.camp.slug
|
||||
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
body {
|
||||
margin-top: 90px;
|
||||
margin-top: 85px;
|
||||
margin-bottom: 35px;
|
||||
}
|
||||
|
||||
|
@ -42,23 +42,7 @@ a, a:active, a:focus {
|
|||
}
|
||||
|
||||
.navbar-fixed-top {
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.nav li a {
|
||||
padding: 30px 15px;
|
||||
}
|
||||
.nav {
|
||||
float: right!important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.nav li {
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
}
|
||||
min-height: 70px;
|
||||
}
|
||||
|
||||
.navbar-toggle .icon-bar {
|
||||
|
@ -121,13 +105,8 @@ footer {
|
|||
}
|
||||
|
||||
.event {
|
||||
max-width: 200px;
|
||||
width: 200px;
|
||||
height: 150px;
|
||||
display: inline-block;
|
||||
margin: 5px 5px;
|
||||
padding: 5px;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.event:hover {
|
||||
|
@ -135,3 +114,4 @@ footer {
|
|||
color: white !important;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue