proof that I did something during 33c3
|
@ -72,7 +72,7 @@ TEMPLATES = [
|
||||||
'django.contrib.messages.context_processors.messages',
|
'django.contrib.messages.context_processors.messages',
|
||||||
'shop.context_processors.current_order',
|
'shop.context_processors.current_order',
|
||||||
'shop.context_processors.user_has_tickets',
|
'shop.context_processors.user_has_tickets',
|
||||||
'camps.context_processors.camps',
|
'camps.context_processors.camp',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -11,8 +11,10 @@ from django.contrib import admin
|
||||||
from django.views.generic import TemplateView, RedirectView
|
from django.views.generic import TemplateView, RedirectView
|
||||||
from django.core.urlresolvers import reverse_lazy
|
from django.core.urlresolvers import reverse_lazy
|
||||||
from camps.views import *
|
from camps.views import *
|
||||||
from info.views import CampInfoView
|
from info.views import *
|
||||||
|
from villages.views import *
|
||||||
|
from program.views import *
|
||||||
|
from sponsors.views import *
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(
|
url(
|
||||||
|
@ -27,24 +29,11 @@ urlpatterns = [
|
||||||
r'^news/',
|
r'^news/',
|
||||||
include('news.urls', namespace='news')
|
include('news.urls', namespace='news')
|
||||||
),
|
),
|
||||||
url(
|
|
||||||
r'^villages/',
|
|
||||||
include('villages.urls', namespace='villages')
|
|
||||||
),
|
|
||||||
url(
|
|
||||||
r'^schedule/',
|
|
||||||
include('program.urls', namespace='schedule')
|
|
||||||
),
|
|
||||||
url(
|
url(
|
||||||
r'^$',
|
r'^$',
|
||||||
TemplateView.as_view(template_name='frontpage.html'),
|
TemplateView.as_view(template_name='frontpage.html'),
|
||||||
name='frontpage'
|
name='frontpage'
|
||||||
),
|
),
|
||||||
url(
|
|
||||||
r'^info/',
|
|
||||||
TemplateView.as_view(template_name='info.html'),
|
|
||||||
name='info'
|
|
||||||
),
|
|
||||||
url(
|
url(
|
||||||
r'^contact/',
|
r'^contact/',
|
||||||
TemplateView.as_view(template_name='contact.html'),
|
TemplateView.as_view(template_name='contact.html'),
|
||||||
|
@ -90,28 +79,63 @@ urlpatterns = [
|
||||||
|
|
||||||
# camp specific urls below here
|
# camp specific urls below here
|
||||||
|
|
||||||
url(r'(?P<camp_slug>[-_\w+]+)/', include([
|
url(
|
||||||
|
r'(?P<camp_slug>[-_\w+]+)/', include([
|
||||||
url(
|
url(
|
||||||
r'^$',
|
r'^$',
|
||||||
CampDetailView.as_view(),
|
CampDetailView.as_view(),
|
||||||
name='camp_detail'
|
name='camp_detail'
|
||||||
),
|
),
|
||||||
|
|
||||||
url(
|
url(
|
||||||
r'^info/$',
|
r'^info/$',
|
||||||
CampInfoView.as_view(),
|
CampInfoView.as_view(),
|
||||||
name='info'
|
name='info'
|
||||||
),
|
),
|
||||||
|
|
||||||
url(
|
url(
|
||||||
r'^schedule/$',
|
r'^schedule/', include([
|
||||||
CampScheduleView.as_view(),
|
url(
|
||||||
name='schedule'
|
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'^(?P<slug>[-_\w+]+)/$',
|
||||||
|
EventDetailView.as_view(),
|
||||||
|
name='event'
|
||||||
|
),
|
||||||
|
])
|
||||||
|
),
|
||||||
|
|
||||||
url(
|
url(
|
||||||
r'^sponsors/$',
|
r'^sponsors/$',
|
||||||
CampSponsorView.as_view(),
|
SponsorIndexView.as_view(),
|
||||||
name='camp_sponsors'
|
name='sponsors'
|
||||||
),
|
),
|
||||||
url(r'^villages/$', include([
|
|
||||||
|
url(
|
||||||
|
r'^villages/', include([
|
||||||
url(
|
url(
|
||||||
r'^$',
|
r'^$',
|
||||||
VillageListView.as_view(),
|
VillageListView.as_view(),
|
||||||
|
@ -137,7 +161,9 @@ urlpatterns = [
|
||||||
VillageDetailView.as_view(),
|
VillageDetailView.as_view(),
|
||||||
name='village_detail'
|
name='village_detail'
|
||||||
),
|
),
|
||||||
])),
|
])
|
||||||
])),
|
),
|
||||||
|
])
|
||||||
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,16 @@ from .models import Camp
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
|
|
||||||
def camps(request):
|
def camp(request):
|
||||||
|
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 {
|
return {
|
||||||
'upcoming_camps': Camp.objects.filter(camp_start__gt=timezone.now()),
|
'camps': Camp.objects.all().order_by('-camp_start'),
|
||||||
'previous_camps': Camp.objects.filter(camp_start__lt=timezone.now()),
|
'camp': camp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
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
|
@ -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',
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,13 +1,14 @@
|
||||||
from django.views.generic.detail import SingleObjectMixin
|
|
||||||
from camps.models import Camp
|
from camps.models import Camp
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
|
|
||||||
|
|
||||||
class CampViewMixin(Object):
|
class CampViewMixin(object):
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
self.camp = get_object_or_404(Camp, slug=self.kwargs.camp_slug)
|
self.camp = get_object_or_404(Camp, slug=self.kwargs['camp_slug'])
|
||||||
return super(CampViewMixin, self).dispatch(request, *args, **kwargs)
|
return super(CampViewMixin, self).dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return self.objects.filter(camp=self.camp)
|
queryset = super(CampViewMixin, self).get_queryset()
|
||||||
|
return queryset.filter(camp=self.camp)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
from .models import *
|
||||||
|
|
||||||
|
admin.site.register(InfoCategory)
|
||||||
|
admin.site.register(InfoItem)
|
||||||
|
|
||||||
# Register your models here.
|
|
||||||
|
|
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.'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -9,6 +9,7 @@ class InfoCategory(CreatedUpdatedModel):
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['-weight', 'headline']
|
ordering = ['-weight', 'headline']
|
||||||
unique_together = (('anchor', 'camp'), ('headline', 'camp'))
|
unique_together = (('anchor', 'camp'), ('headline', 'camp'))
|
||||||
|
verbose_name_plural = "Info Categories"
|
||||||
|
|
||||||
camp = models.ForeignKey(
|
camp = models.ForeignKey(
|
||||||
'camps.Camp',
|
'camps.Camp',
|
||||||
|
@ -26,14 +27,18 @@ class InfoCategory(CreatedUpdatedModel):
|
||||||
)
|
)
|
||||||
|
|
||||||
weight = models.PositiveIntegerField(
|
weight = models.PositiveIntegerField(
|
||||||
help_text = 'Determines sorting/ordering. Heavier categories sink to the bottom. Categories with the same weight are ordered alphabetically.'
|
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):
|
def clean(self):
|
||||||
if InfoItem.objects.filter(camp=self.camp, anchor=self.anchor).exists():
|
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)
|
# 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'})
|
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 InfoItem(CreatedUpdatedModel):
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -60,11 +65,12 @@ class InfoItem(CreatedUpdatedModel):
|
||||||
)
|
)
|
||||||
|
|
||||||
weight = models.PositiveIntegerField(
|
weight = models.PositiveIntegerField(
|
||||||
help_text = 'Determines sorting/ordering. Heavier items sink to the bottom. Items with the same weight are ordered alphabetically.'
|
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):
|
def clean(self):
|
||||||
if InfoCategory.objects.filter(camp=self.camp, anchor=self.anchor).exists():
|
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)
|
# 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'})
|
raise ValidationError({'anchor': 'Anchor is already in use on an info category for this camp'})
|
||||||
|
|
||||||
|
|
|
@ -26,9 +26,10 @@ Info | {{ block.super }}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
{% if categories %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<h2>Table of Contents</h2>
|
<h3>Table of Contents</h3>
|
||||||
<p class="list-group">
|
<p class="list-group">
|
||||||
{% for category in categories %}
|
{% for category in categories %}
|
||||||
<a href="#{{ category.anchor }}" class="list-group-item">{{ category.headline }}</a>
|
<a href="#{{ category.anchor }}" class="list-group-item">{{ category.headline }}</a>
|
||||||
|
@ -41,14 +42,13 @@ Info | {{ block.super }}
|
||||||
<span class="anchor" id="{{ category.anchor }}"></span>
|
<span class="anchor" id="{{ category.anchor }}"></span>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<h2>When is BornHack happening?</h2>
|
<h2>{{ category.title }}</h2>
|
||||||
<div class="panel-group">
|
<div class="panel-group">
|
||||||
{% for item in category.infoitems %}
|
{% for item in category.infoitems.all %}
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<span class="sub-anchor" id="{{ item.anchor }}"></span>
|
<span class="sub-anchor" id="{{ item.anchor }}"></span>
|
||||||
<h4 class="panel-title">
|
<h4 class="panel-title">{{ item.headline }}
|
||||||
{{ item.title }}
|
|
||||||
<a href="#{{ item.anchor }}">
|
<a href="#{{ item.anchor }}">
|
||||||
<i class="glyphicon glyphicon-link"></i>
|
<i class="glyphicon glyphicon-link"></i>
|
||||||
</a>
|
</a>
|
||||||
|
@ -63,23 +63,8 @@ Info | {{ block.super }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
<script src="{% static 'js/leaflet.js' %}"></script>
|
<h3>No info found for {{ camp.title }}</h3>
|
||||||
<script>
|
{% endif %}
|
||||||
var map = L.map('map', {center: [55.131520, 14.903000], zoom: 10});
|
|
||||||
|
|
||||||
L.tileLayer(
|
|
||||||
'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
|
|
||||||
{
|
|
||||||
attribution: '© <a href="http://osm.org/copyright" target="_blank">OpenStreetMap</a> contributors',
|
|
||||||
}
|
|
||||||
).addTo(map);
|
|
||||||
|
|
||||||
var camp_latlong = [55.011520, 14.975360];
|
|
||||||
L.marker(camp_latlong).addTo(map);
|
|
||||||
|
|
||||||
L.marker(camp_latlong).addTo(map)
|
|
||||||
.bindPopup('<strong>Coordinates:</strong><br>55.011520, 14.975360<br><strong>Address:</strong><br>Baunevej 11, 3720 Aakirkeby')
|
|
||||||
.openPopup();
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
|
@ -2,16 +2,15 @@ from django.shortcuts import render
|
||||||
from django.views.generic import ListView, DetailView
|
from django.views.generic import ListView, DetailView
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from .models import *
|
from .models import *
|
||||||
|
from camps.mixins import CampViewMixin
|
||||||
|
|
||||||
|
class CampInfoView(CampViewMixin, ListView):
|
||||||
class CampInfoView(ListView):
|
|
||||||
model = InfoCategory
|
model = InfoCategory
|
||||||
template_name = 'info.html'
|
template_name = 'info.html'
|
||||||
context_object_name = 'categories'
|
context_object_name = 'categories'
|
||||||
|
|
||||||
def get_queryset(self, **kwargs):
|
def get_queryset(self):
|
||||||
return InfoCategory.objects.filter(
|
queryset = super(CampInfoView, self).get_queryset()
|
||||||
camp__slug=self.kwargs['camp_slug']
|
# do not show categories with 0 items
|
||||||
)
|
return queryset.exclude(infoitems__isnull=True)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ from collections import OrderedDict
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
from django.views.generic import ListView, TemplateView, DetailView
|
from django.views.generic import ListView, TemplateView, DetailView
|
||||||
from camp.mixins import CampViewMixin
|
from camps.mixins import CampViewMixin
|
||||||
|
|
||||||
from . import models
|
from . import models
|
||||||
|
|
||||||
|
|
0
sponsors/__init__.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
|
@ -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
5
sponsors/models.py
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
# Create your models here.
|
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
|
@ -0,0 +1,9 @@
|
||||||
|
from django.views.generic import TemplateView
|
||||||
|
from camps.mixins import CampViewMixin
|
||||||
|
|
||||||
|
|
||||||
|
class SponsorIndexView(CampViewMixin, TemplateView):
|
||||||
|
def get_template_name(self):
|
||||||
|
return '%s-sponsors.html' % self.camp.slug
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
body {
|
body {
|
||||||
margin-top: 90px;
|
margin-top: 85px;
|
||||||
margin-bottom: 35px;
|
margin-bottom: 35px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,23 +42,7 @@ a, a:active, a:focus {
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar-fixed-top {
|
.navbar-fixed-top {
|
||||||
min-height: 80px;
|
min-height: 70px;
|
||||||
}
|
|
||||||
|
|
||||||
@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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar-toggle .icon-bar {
|
.navbar-toggle .icon-bar {
|
||||||
|
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
BIN
static_src/img/bornhack-2016/logo-new.png
Normal file
After Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 5.8 KiB |
BIN
static_src/img/bornhack-2016/logo.png
Normal file
After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
BIN
static_src/img/logo-small.png
Normal file
After Width: | Height: | Size: 4 KiB |
|
@ -1,5 +1,7 @@
|
||||||
{% load static from staticfiles %}
|
{% load static from staticfiles %}
|
||||||
{% load bootstrap3 %}
|
{% load bootstrap3 %}
|
||||||
|
{% static "" as baseurl %}
|
||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
|
@ -20,7 +22,6 @@
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<nav class="navbar navbar-fixed-top">
|
<nav class="navbar navbar-fixed-top">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="navbar-header">
|
<div class="navbar-header">
|
||||||
|
@ -31,36 +32,29 @@
|
||||||
<span class="icon-bar"></span>
|
<span class="icon-bar"></span>
|
||||||
</button>
|
</button>
|
||||||
<a class="navbar-brand" href="/">
|
<a class="navbar-brand" href="/">
|
||||||
<img src="{% static 'img/logo-new.png' %}" class="img-responsive" />
|
{% if request.resolver_match.kwargs.camp_slug %}
|
||||||
|
<img src="{{ baseurl }}/img/{{ camp.slug }}/{{ camp.slug }}-logo-small.png" class="img-responsive" />
|
||||||
|
{% else %}
|
||||||
|
<img src="{% static 'img/logo-small.png' %}" class="img-responsive" />
|
||||||
|
{% endif %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="navbar" class="navbar-collapse collapse">
|
<div id="navbar" class="navbar-collapse collapse">
|
||||||
<ul class="nav navbar-nav">
|
<ul class="nav navbar-nav">
|
||||||
<li><a href="{% url 'news:index' %}">News</a></li>
|
<li><a href="{% url 'news:index' %}">News</a></li>
|
||||||
<li><a href="{% url 'shop:index' %}">Shop</a></li>
|
<li><a href="{% url 'shop:index' %}">Shop</a></li>
|
||||||
<li class="dropdown">
|
<li class="dropdown">
|
||||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Camps <span class="caret"></span></a>
|
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{{ camp.title|default:"Camps" }}<span class="caret"></span></a>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
{% for camp in upcoming_camps %}
|
{% for camp in camps %}
|
||||||
<li><a href="{% url 'camp_detail' camp_slug=camp.slug %}">{{ camp.title }}</a></li>
|
|
||||||
{% endfor %}
|
|
||||||
<li role="separator" class="divider"></li>
|
|
||||||
{% for camp in previous_camps %}
|
|
||||||
<li><a href="{% url 'camp_detail' camp_slug=camp.slug %}">{{ camp.title }}</a></li>
|
<li><a href="{% url 'camp_detail' camp_slug=camp.slug %}">{{ camp.title }}</a></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
{# only include camp specific menu items when relevant #}
|
</ul>
|
||||||
{% if request.resolver_match.kwargs.camp_slug %}
|
<ul class="nav navbar-nav navbar-right">
|
||||||
<li><a href="{% url 'camp_info' camp_slug=camp.slug %}">Info</a></li>
|
|
||||||
<li><a href="{% url 'camp_villages' camp_slug=camp.slug %}">Villages</a></li>
|
|
||||||
<li><a href="{% url 'camp_schedule' camp_slug=camp.slug %}">Schedule</a></li>
|
|
||||||
<li><a href="{% url 'camp_sponsors' camp_slug=camp.slug %}">Sponsors</a></li>
|
|
||||||
{% endif %}
|
|
||||||
<li><a href="{% url 'contact' %}">Contact</a></li>
|
|
||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated %}
|
||||||
<li><a href="{% url 'profiles:detail' %}">Profile</a></li>
|
<li><a href="{% url 'profiles:detail' %}">Account</a></li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li><a href="{% url 'account_login' %}">Login</a></li>
|
<li><a href="{% url 'account_login' %}">Login</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -70,6 +64,18 @@
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div id="main" class="container container-fluid">
|
<div id="main" class="container container-fluid">
|
||||||
|
{% if camp %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="btn-group btn-group-justified">
|
||||||
|
<a class="btn {% if request.resolver_match.url_name == "camp_detail" %}btn-primary{% else %}btn-default{% endif %}" href="{% url 'camp_detail' camp_slug=camp.slug %}">{{ camp.title }}</a>
|
||||||
|
<a class="btn {% if request.resolver_match.url_name == "info" %}btn-primary{% else %}btn-default{% endif %}" href="{% url 'info' camp_slug=camp.slug %}">Info</a></li>
|
||||||
|
<a class="btn {% if request.resolver_match.url_name == "village_list" %}btn-primary{% else %}btn-default{% endif %}" href="{% url 'village_list' camp_slug=camp.slug %}">Villages</a>
|
||||||
|
<a class="btn {% if request.resolver_match.url_name == "schedule_index" %}btn-primary{% else %}btn-default{% endif %}" href="{% url 'schedule_index' camp_slug=camp.slug %}">Schedule</a>
|
||||||
|
<a class="btn {% if request.resolver_match.url_name == "sponsors" %}btn-primary{% else %}btn-default{% endif %}" href="{% url 'sponsors' camp_slug=camp.slug %}">Sponsors</a>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% bootstrap_messages %}
|
{% bootstrap_messages %}
|
||||||
{% block content %}{% endblock %}
|
{% block content %}{% endblock %}
|
||||||
<footer class="row">
|
<footer class="row">
|
||||||
|
@ -78,6 +84,7 @@
|
||||||
<a href="{% url 'general-terms' %}">General Terms & Conditions</a> |
|
<a href="{% url 'general-terms' %}">General Terms & Conditions</a> |
|
||||||
<a href="{% url 'conduct' %}">Code of Conduct</a> |
|
<a href="{% url 'conduct' %}">Code of Conduct</a> |
|
||||||
<a href="{% url 'privacy-policy' %}">Privacy Policy</a>
|
<a href="{% url 'privacy-policy' %}">Privacy Policy</a>
|
||||||
|
<a href="{% url 'contact' %}">Contact</a>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import uuid
|
import uuid
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ class CleanedModel(models.Model):
|
||||||
print(message)
|
print(message)
|
||||||
# dont save, just return
|
# dont save, just return
|
||||||
return
|
return
|
||||||
super(CreatedUpdatedModel, self).save(**kwargs)
|
super(CleanedModel, self).save(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
class UUIDModel(CleanedModel):
|
class UUIDModel(CleanedModel):
|
||||||
|
|
22
villages/migrations/0007_village_camp.py
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.4 on 2016-12-28 22:08
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('camps', '0011_auto_20161228_1750'),
|
||||||
|
('villages', '0006_remove_village_camp'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='village',
|
||||||
|
name='camp',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='camps.Camp'),
|
||||||
|
),
|
||||||
|
]
|
21
villages/migrations/0008_auto_20161228_2209.py
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.4 on 2016-12-28 22:09
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('villages', '0007_village_camp'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='village',
|
||||||
|
name='camp',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='camps.Camp'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -15,7 +15,7 @@ class Village(CreatedUpdatedModel, UUIDModel):
|
||||||
ordering = ['name']
|
ordering = ['name']
|
||||||
|
|
||||||
contact = models.ForeignKey('auth.User')
|
contact = models.ForeignKey('auth.User')
|
||||||
|
camp = models.ForeignKey('camps.Camp')
|
||||||
name = models.CharField(max_length=255)
|
name = models.CharField(max_length=255)
|
||||||
slug = models.SlugField(max_length=255, blank=True)
|
slug = models.SlugField(max_length=255, blank=True)
|
||||||
description = models.TextField(
|
description = models.TextField(
|
||||||
|
@ -37,7 +37,7 @@ class Village(CreatedUpdatedModel, UUIDModel):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse_lazy('villages:detail', kwargs={'slug': self.slug})
|
return reverse_lazy('village_detail', kwargs={'camp_slug': self.camp.slug, 'slug': self.slug})
|
||||||
|
|
||||||
def save(self, **kwargs):
|
def save(self, **kwargs):
|
||||||
if (
|
if (
|
||||||
|
|
|
@ -8,6 +8,6 @@
|
||||||
<button type="submit" class="btn btn-danger form-control">Confirm</button>
|
<button type="submit" class="btn btn-danger form-control">Confirm</button>
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
<a href="{% url 'villages:detail' slug=village.slug %}" class="btn btn-default form-control">Cancel</a>
|
<a href="{% url 'village_detail' slug=village.slug %}" class="btn btn-default form-control">Cancel</a>
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -13,8 +13,8 @@ Village: {{ village.name }} | {{ block.super }}
|
||||||
|
|
||||||
{% if user == village.contact %}
|
{% if user == village.contact %}
|
||||||
<hr />
|
<hr />
|
||||||
<a href="{% url 'villages:update' slug=village.slug %}" class="btn btn-primary">Edit</a>
|
<a href="{% url 'village_update' camp_slug=village.camp.slug slug=village.slug %}" class="btn btn-primary">Edit</a>
|
||||||
<a href="{% url 'villages:delete' slug=village.slug %}" class="btn btn-danger">Delete</a>
|
<a href="{% url 'village_delete' camp_slug=village.camp.slug slug=village.slug %}" class="btn btn-danger">Delete</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -6,7 +6,7 @@ Villages | {{ block.super }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
<h2>Villages</h2>
|
||||||
<p>
|
<p>
|
||||||
If this is your first hackercamp the term 'Village' might be confusing but it
|
If this is your first hackercamp the term 'Village' might be confusing but it
|
||||||
is fairly simple: a village is just a spot on the campsite where you and a
|
is fairly simple: a village is just a spot on the campsite where you and a
|
||||||
|
@ -16,17 +16,15 @@ Villages | {{ block.super }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p class="lead">
|
<p class="lead">
|
||||||
<a href="https://bornhack.dk/shop/?category=villages">
|
It is also possible to rent a tent, chairs and tables in the shop!
|
||||||
It is also possible to rent a tent, chairs and tables for villages here.
|
|
||||||
</a>
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated %}
|
||||||
<a href="{% url 'villages:create' %}" class="btn btn-primary">Create a village</a>
|
<a href="{% url 'village_create' camp_slug=camp.slug %}" class="btn btn-primary">Create a village</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
{% if villages %}
|
||||||
<table class="table table-hover table-condensed table-striped">
|
<table class="table table-hover table-condensed table-striped">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -39,7 +37,7 @@ Villages | {{ block.super }}
|
||||||
{% for village in villages %}
|
{% for village in villages %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a href="{% url 'villages:detail' slug=village.slug %}">
|
<a href="{% url 'village_detail' camp_slug=camp.slug slug=village.slug %}">
|
||||||
{{ village.name }}
|
{{ village.name }}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
@ -53,5 +51,7 @@ Villages | {{ block.super }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
{% else %}
|
||||||
|
<h4>No villages for <b>{{ camp.title }}</b> yet!</h4>
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -2,21 +2,20 @@ from django.http import Http404
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.core.urlresolvers import reverse_lazy
|
from django.core.urlresolvers import reverse_lazy
|
||||||
from django.http import HttpResponseRedirect
|
from django.http import HttpResponseRedirect
|
||||||
from django.views.generic import (
|
from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView
|
||||||
ListView, DetailView, CreateView, UpdateView, DeleteView
|
|
||||||
)
|
|
||||||
from django.views.generic.detail import SingleObjectMixin
|
from django.views.generic.detail import SingleObjectMixin
|
||||||
|
|
||||||
from .models import Village
|
from .models import Village
|
||||||
|
from camps.models import Camp
|
||||||
|
from camps.mixins import CampViewMixin
|
||||||
|
|
||||||
|
|
||||||
class VillageListView(ListView):
|
class VillageListView(CampViewMixin, ListView):
|
||||||
queryset = Village.objects.not_deleted()
|
queryset = Village.objects.not_deleted()
|
||||||
template_name = 'village_list.html'
|
template_name = 'village_list.html'
|
||||||
context_object_name = 'villages'
|
context_object_name = 'villages'
|
||||||
|
|
||||||
|
|
||||||
class VillageDetailView(DetailView):
|
class VillageDetailView(CampViewMixin, DetailView):
|
||||||
queryset = Village.objects.not_deleted()
|
queryset = Village.objects.not_deleted()
|
||||||
template_name = 'village_detail.html'
|
template_name = 'village_detail.html'
|
||||||
context_object_name = 'village'
|
context_object_name = 'village'
|
||||||
|
@ -31,6 +30,7 @@ class VillageCreateView(LoginRequiredMixin, CreateView):
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
village = form.save(commit=False)
|
village = form.save(commit=False)
|
||||||
village.contact = self.request.user
|
village.contact = self.request.user
|
||||||
|
village.camp = Camp.objects.get(slug=self.request.session['campslug'])
|
||||||
village.save()
|
village.save()
|
||||||
return HttpResponseRedirect(village.get_absolute_url())
|
return HttpResponseRedirect(village.get_absolute_url())
|
||||||
|
|
||||||
|
@ -64,3 +64,4 @@ class VillageDeleteView(EnsureUserOwnsVillageMixin, LoginRequiredMixin, DeleteVi
|
||||||
success_url = reverse_lazy('villages:list')
|
success_url = reverse_lazy('villages:list')
|
||||||
template_name = 'village_confirm_delete.html'
|
template_name = 'village_confirm_delete.html'
|
||||||
context_object_name = 'village'
|
context_object_name = 'village'
|
||||||
|
|
||||||
|
|