This commit is contained in:
Thomas Steen Rasmussen 2016-07-11 13:47:06 +02:00
commit e3facc15b5
32 changed files with 447 additions and 23 deletions

View file

@ -29,6 +29,7 @@ INSTALLED_APPS = [
'shop', 'shop',
'news', 'news',
'utils', 'utils',
'villages',
'allauth', 'allauth',
'allauth.account', 'allauth.account',

View file

@ -7,7 +7,7 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>Bornhack</title> <title>{% block title %}BornHack{% endblock %}</title>
<!-- Bootstrap core CSS --> <!-- Bootstrap core CSS -->
<link href="{% static 'css/bootstrap.min.css' %}" rel="stylesheet"> <link href="{% static 'css/bootstrap.min.css' %}" rel="stylesheet">
@ -42,19 +42,8 @@
{% if current_camp.shop_open %} {% if current_camp.shop_open %}
<li><a href="{% url 'shop:index' %}">Shop</a></li> <li><a href="{% url 'shop:index' %}">Shop</a></li>
{% if current_order and current_order.get_number_of_items %}
<li>
<a href="{% url 'shop:order_detail' pk=current_order.pk %}">
<i class="glyphicon glyphicon-shopping-cart"></i>
<span class="badge">
{{ current_order.get_number_of_items }}
</span>
</a>
</li>
{% endif %}
{% endif %} {% endif %}
<li><a href="{% url 'villages:list' %}">Villages</a></li>
<li><a href="{% url 'call-for-speakers' %}">Speakers</a></li> <li><a href="{% url 'call-for-speakers' %}">Speakers</a></li>
<li><a href="{% url 'call-for-sponsors' %}">Sponsors</a></li> <li><a href="{% url 'call-for-sponsors' %}">Sponsors</a></li>
<li><a href="{% url 'contact' %}">Contact</a></li> <li><a href="{% url 'contact' %}">Contact</a></li>

View file

@ -1,5 +1,9 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% block title %}
Code of Conduct | {{ block.super }}
{% endblock %}
{% block content %} {% block content %}
<h2>Code of Conduct</h2> <h2>Code of Conduct</h2>

View file

@ -1,5 +1,9 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% block title %}
Contact | {{ block.super }}
{% endblock %}
{% block content %} {% block content %}
<p class="lead"> <p class="lead">

View file

@ -1,6 +1,10 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% load static from staticfiles %} {% load static from staticfiles %}
{% block title %}
Info | {{ block.super }}
{% endblock %}
{% block extra_head %} {% block extra_head %}
<link rel="stylesheet" href="{% static 'css/leaflet.css' %}" /> <link rel="stylesheet" href="{% static 'css/leaflet.css' %}" />
{% endblock %} {% endblock %}
@ -201,16 +205,22 @@
confusing but it is fairly simple: a village is just a spot on the confusing but it is fairly simple: a village is just a spot on the
campsite where you and a bunch of your friends/likeminded people camp campsite where you and a bunch of your friends/likeminded people camp
together. Apart from peoples individual tents which they sleep in, many together. Apart from peoples individual tents which they sleep in, many
villages bring a large common tent where you can hack and hang out during the day. villages bring a large common tent where you can hack and hang out
during the day.
</p> </p>
<p>Villages can also rent village tents. The details are not in place yet, <p>Villages can also rent village tents via us, head over to the
but stay tuned for the announcement. The idea is that you order and pay <a href="{% url 'shop:index' %}?category=villages">
for a large tent which will then be setup and ready when you arrive. The villages section of the shop</a>.
tents have wooden floors and you can rent folding tables and chairs as needed. The tents will be ready for when you arrive and will be teared down
again saturday the 3rd of September at 12:00. The tents have optional
floors and you can rent folding tables and chairs as needed. You do not
have to register a village or have a concept to rent a big tent.
</p>
<p><a href="{% url 'villages:list' %}">You can register your village registration here</a>!
If you don't want to make your own village you will possibly be able to
find and join one that suits your interests.
Get in touch if you have any questions!
</p> </p>
<p>Village registration is not finished yet, but stay tuned!
If you don't want to make your own village you will likely
be able to find and join one that suits your interests.</p>
</div> </div>
</div> </div>
@ -231,7 +241,6 @@
<dt>Kiosk</dt> <dt>Kiosk</dt>
<dd>We sell a few beverages and sweets as well as practical things like coal for the barbeque. We will accept the same payment methods at the venue as we do on the website: cash, cards, and blockchain.</dd> <dd>We sell a few beverages and sweets as well as practical things like coal for the barbeque. We will accept the same payment methods at the venue as we do on the website: cash, cards, and blockchain.</dd>
</dl> </dl>
</p>
</div> </div>
</div> </div>

View file

@ -1,5 +1,9 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% block title %}
General Terms and Conditions | {{ block.super }}
{% endblock %}
{% block content %} {% block content %}
<h2>Terms and Conditions for BornHack IvS sale and delivery of (i) merchandise (ii) tickets and/or (iii) accommodation</h2> <h2>Terms and Conditions for BornHack IvS sale and delivery of (i) merchandise (ii) tickets and/or (iii) accommodation</h2>

View file

@ -1,5 +1,9 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% block title %}
Privacy Policy | {{ block.super }}
{% endblock %}
{% block content %} {% block content %}
<h2>Privacy and cookie policy</h2> <h2>Privacy and cookie policy</h2>

View file

@ -1,5 +1,9 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% block title %}
Call for Speakers | {{ block.super }}
{% endblock %}
{% block content %} {% block content %}
<h2>BornHack 2016: Call for Speakers</h2> <h2>BornHack 2016: Call for Speakers</h2>

View file

@ -1,5 +1,9 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% block title %}
Call for Sponsors | {{ block.super }}
{% endblock %}
{% block content %} {% block content %}
<h2>Sponsoring</h2> <h2>Sponsoring</h2>

View file

@ -74,6 +74,10 @@ 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'^accounts/', include('allauth.urls')), url(r'^accounts/', include('allauth.urls')),
url(r'^admin/', include(admin.site.urls)), url(r'^admin/', include(admin.site.urls)),
] ]

View file

@ -1,6 +1,10 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% load commonmark %} {% load commonmark %}
{% block title %}
{{ news_item.title }} | {{ block.super }}
{% endblock %}
{% block content %} {% block content %}
<div> <div>
{% if not_public %} {% if not_public %}

View file

@ -1,6 +1,10 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% load commonmark %} {% load commonmark %}
{% block title %}
News | {{ block.super }}
{% endblock %}
{% block content %} {% block content %}
{% for item in news_items %} {% for item in news_items %}
<div> <div>

View file

@ -3,6 +3,10 @@
{% load commonmark %} {% load commonmark %}
{% load shop_tags %} {% load shop_tags %}
{% block title %}
{{ product.name }} | {{ block.super }}
{% endblock %}
{% block shop_content %} {% block shop_content %}
<div class="row"> <div class="row">

View file

@ -19,7 +19,21 @@
{% if has_tickets %} {% if has_tickets %}
<li class="pull-right"><a href="{% url 'shop:ticket_list' %}">Tickets</a></li> <li class="pull-right"><a href="{% url 'shop:ticket_list' %}">Tickets</a></li>
{% endif %} {% endif %}
<li class="pull-right no-before"><a href="{% url 'shop:order_list' %}">Orders</a></li> <li class="pull-right"><a href="{% url 'shop:order_list' %}">Orders</a></li>
<li class="pull-right no-before">
{% if current_order and current_order.get_number_of_items %}
<a href="{% url 'shop:order_detail' pk=current_order.pk %}">
{% endif %}
<i class="glyphicon glyphicon-shopping-cart"></i>
<span class="badge">
{{ current_order.get_number_of_items|default:0 }}
</span>
{% if current_order and current_order.get_number_of_items %}
</a>
{% endif %}
</li>
{% endif %} {% endif %}
</ol> </ol>
</div> </div>

View file

@ -2,6 +2,10 @@
{% load bootstrap3 %} {% load bootstrap3 %}
{% load shop_tags %} {% load shop_tags %}
{% block title %}
Shop | {{ block.super }}
{% endblock %}
{% block shop_content %} {% block shop_content %}
<div class="row"> <div class="row">

0
villages/__init__.py Normal file
View file

17
villages/admin.py Normal file
View file

@ -0,0 +1,17 @@
from django.contrib import admin
from .models import Village
@admin.register(Village)
class VillageAdmin(admin.ModelAdmin):
list_display = [
'name',
'private',
'deleted',
]
list_filter = [
'private',
'deleted',
]

7
villages/apps.py Normal file
View file

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

9
villages/managers.py Normal file
View file

@ -0,0 +1,9 @@
from django.db.models import QuerySet
class VillageQuerySet(QuerySet):
def not_deleted(self):
return self.filter(
deleted=False
)

View file

@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.6 on 2016-07-05 21:38
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
initial = True
dependencies = [
('camps', '0005_auto_20160510_2011'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Village',
fields=[
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('created', models.DateTimeField(auto_now_add=True)),
('updated', models.DateTimeField(auto_now=True)),
('name', models.CharField(max_length=255)),
('slug', models.SlugField(blank=True, max_length=255)),
('description', models.TextField()),
('open', models.BooleanField(default=False, help_text='Is this village open for others to join?')),
('camp', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='camps.Camp')),
('contact', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
'ordering': ['name'],
},
),
]

View file

@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.6 on 2016-07-05 21:54
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('villages', '0001_initial'),
]
operations = [
migrations.RemoveField(
model_name='village',
name='open',
),
migrations.AddField(
model_name='village',
name='private',
field=models.BooleanField(default=True, help_text='Check if your village is privately organized'),
),
]

View file

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.6 on 2016-07-05 21:59
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('villages', '0002_auto_20160705_2154'),
]
operations = [
migrations.AlterField(
model_name='village',
name='private',
field=models.BooleanField(default=False, help_text='Check if your village is privately organized'),
),
]

View file

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.6 on 2016-07-10 16:58
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('villages', '0003_auto_20160705_2159'),
]
operations = [
migrations.AddField(
model_name='village',
name='deleted',
field=models.BooleanField(default=False),
),
]

View file

72
villages/models.py Normal file
View file

@ -0,0 +1,72 @@
from __future__ import unicode_literals
from django.core.urlresolvers import reverse_lazy
from django.db import models
from django.utils.text import slugify
from camps.models import Camp
from utils.models import CreatedUpdatedModel, UUIDModel
from .managers import VillageQuerySet
class Village(CreatedUpdatedModel, UUIDModel):
class Meta:
ordering = ['name']
camp = models.ForeignKey('camps.Camp')
contact = models.ForeignKey('auth.User')
name = models.CharField(max_length=255)
slug = models.SlugField(max_length=255, blank=True)
description = models.TextField(
help_text="A descriptive text about your village. Markdown is supported."
)
private = models.BooleanField(
default=False,
help_text='Check if your village is privately organized'
)
deleted = models.BooleanField(
default=False,
)
objects = VillageQuerySet.as_manager()
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse_lazy('villages:detail', kwargs={'slug': self.slug})
def save(self, **kwargs):
if (
not self.pk or
not self.slug or
Village.objects.filter(slug=self.slug).count() > 1
):
slug = slugify(self.name)
incrementer = 1
# We have to make sure that the slug won't clash with current slugs
while Village.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
if not hasattr(self, 'camp'):
self.camp = Camp.objects.current()
super(Village, self).save(**kwargs)
def delete(self, using=None, keep_parents=False):
self.deleted = True
self.save()

View file

@ -0,0 +1,13 @@
{% extends 'base.html' %}
{% load commonmark %}
{% block content %}
<form action="" method="post" class="col-md-6 col-md-offset-3">{% csrf_token %}
<p>Are you sure you want to delete the village "{{ village }}"?</p>
<button type="submit" class="btn btn-danger form-control">Confirm</button>
<br />
<br />
<a href="{% url 'villages:detail' slug=village.slug %}" class="btn btn-default form-control">Cancel</a>
</form>
{% endblock %}

View file

@ -0,0 +1,16 @@
{% extends 'base.html' %}
{% load commonmark %}
{% block content %}
<h3>{{ village.name }}</h3>
{{ village.description|commonmark }}
{% if user == village.contact %}
<hr />
<a href="{% url 'villages:update' slug=village.slug %}" class="btn btn-primary">Edit</a>
<a href="{% url 'villages:delete' slug=village.slug %}" class="btn btn-danger">Delete</a>
{% endif %}
{% endblock %}

View file

@ -0,0 +1,16 @@
{% extends 'base.html' %}
{% load bootstrap3 %}
{% block content %}
<form method="post">
{% csrf_token %}
{% bootstrap_form form %}
<hr />
<button class="btn btn-primary pull-right" type="submit">Save</button>
</form>
{% endblock %}

View file

@ -0,0 +1,52 @@
{% extends 'base.html' %}
{% block content %}
<p>
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
bunch of your friends/likeminded people camp together. Apart from peoples
individual tents which they sleep in, many villages bring a large common tent
where you can hack and hang out during the day.
</p>
<p class="lead">
<a href="https://bornhack.dk/shop/?category=villages">
It is also possible to rent a tent, chairs and tables for villages here.
</a>
</p>
{% if user.is_authenticated %}
<a href="{% url 'villages:create' %}" class="btn btn-primary">Create a village</a>
{% endif %}
<hr />
<table class="table table-hover table-condensed table-striped">
<thead>
<tr>
<th>Name</th>
<th>Description</th>
<th>Public</th>
</tr>
</thead>
<tbody>
{% for village in villages %}
<tr>
<td>
<a href="{% url 'villages:detail' slug=village.slug %}">
{{ village.name }}
</a>
</td>
<td>
{{ village.description|truncatewords:15 }}
</td>
<td>
<i class="glyphicon glyphicon-{% if village.private %}remove{% else %}ok{% endif %}"></i>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}

3
villages/tests.py Normal file
View file

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

10
villages/urls.py Normal file
View file

@ -0,0 +1,10 @@
from django.conf.urls import url
from views import *
urlpatterns = [
url(r'^$', VillageListView.as_view(), name='list'),
url(r'create/$', VillageCreateView.as_view(), name='create'),
url(r'(?P<slug>[-_\w+]+)/delete/$', VillageDeleteView.as_view(), name='delete'),
url(r'(?P<slug>[-_\w+]+)/edit/$', VillageUpdateView.as_view(), name='update'),
url(r'(?P<slug>[-_\w+]+)/$', VillageDetailView.as_view(), name='detail'),
]

50
villages/views.py Normal file
View file

@ -0,0 +1,50 @@
from django.core.urlresolvers import reverse_lazy
from django.http import HttpResponseRedirect
from django.views.generic import (
ListView, DetailView, CreateView, UpdateView, DeleteView
)
from .models import (
Village,
)
class VillageListView(ListView):
queryset = Village.objects.not_deleted()
template_name = 'village_list.html'
context_object_name = 'villages'
class VillageDetailView(DetailView):
queryset = Village.objects.not_deleted()
template_name = 'village_detail.html'
context_object_name = 'village'
class VillageCreateView(CreateView):
model = Village
template_name = 'village_form.html'
fields = ['name', 'description', 'private']
success_url = reverse_lazy('villages:list')
def form_valid(self, form):
village = form.save(commit=False)
village.contact = self.request.user
village.save()
return HttpResponseRedirect(village.get_absolute_url())
class VillageUpdateView(UpdateView):
model = Village
queryset = Village.objects.not_deleted()
template_name = 'village_form.html'
fields = ['name', 'description', 'private']
def get_success_url(self):
return self.get_object().get_absolute_url()
class VillageDeleteView(DeleteView):
model = Village
success_url = reverse_lazy('villages:list')
template_name = 'village_confirm_delete.html'
context_object_name = 'village'