diff --git a/bornhack/settings/base.py b/bornhack/settings/base.py index 4b7c2934..269257f9 100644 --- a/bornhack/settings/base.py +++ b/bornhack/settings/base.py @@ -29,6 +29,7 @@ INSTALLED_APPS = [ 'shop', 'news', 'utils', + 'villages', 'allauth', 'allauth.account', diff --git a/bornhack/templates/base.html b/bornhack/templates/base.html index 0a8a2b17..cbc2ad57 100644 --- a/bornhack/templates/base.html +++ b/bornhack/templates/base.html @@ -7,7 +7,7 @@ - Bornhack + {% block title %}BornHack{% endblock %} @@ -42,19 +42,8 @@ {% if current_camp.shop_open %}
  • Shop
  • - - {% if current_order and current_order.get_number_of_items %} -
  • - - - - {{ current_order.get_number_of_items }} - - -
  • - {% endif %} - {% endif %} +
  • Villages
  • Speakers
  • Sponsors
  • Contact
  • diff --git a/bornhack/templates/coc.html b/bornhack/templates/coc.html index f735454b..1dccf57d 100644 --- a/bornhack/templates/coc.html +++ b/bornhack/templates/coc.html @@ -1,5 +1,9 @@ {% extends 'base.html' %} +{% block title %} +Code of Conduct | {{ block.super }} +{% endblock %} + {% block content %}

    Code of Conduct

    diff --git a/bornhack/templates/contact.html b/bornhack/templates/contact.html index 0b1e2e31..e9f5dafb 100644 --- a/bornhack/templates/contact.html +++ b/bornhack/templates/contact.html @@ -1,5 +1,9 @@ {% extends 'base.html' %} +{% block title %} +Contact | {{ block.super }} +{% endblock %} + {% block content %}

    diff --git a/bornhack/templates/info.html b/bornhack/templates/info.html index 8582bfb0..8fb93f0b 100644 --- a/bornhack/templates/info.html +++ b/bornhack/templates/info.html @@ -1,6 +1,10 @@ {% extends 'base.html' %} {% load static from staticfiles %} +{% block title %} +Info | {{ block.super }} +{% endblock %} + {% block extra_head %} {% endblock %} @@ -201,16 +205,22 @@ 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. + villages bring a large common tent where you can hack and hang out + during the day.

    -

    Villages can also rent village tents. The details are not in place yet, - but stay tuned for the announcement. The idea is that you order and pay - for a large tent which will then be setup and ready when you arrive. The - tents have wooden floors and you can rent folding tables and chairs as needed. +

    Villages can also rent village tents via us, head over to the + + villages section of the shop. + 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. +

    +

    You can register your village registration here! + 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!

    -

    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.

    @@ -231,7 +241,6 @@
    Kiosk
    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.
    -

    diff --git a/bornhack/templates/legal/general_terms_and_conditions.html b/bornhack/templates/legal/general_terms_and_conditions.html index 6a66eb37..0cbf8360 100644 --- a/bornhack/templates/legal/general_terms_and_conditions.html +++ b/bornhack/templates/legal/general_terms_and_conditions.html @@ -1,5 +1,9 @@ {% extends 'base.html' %} +{% block title %} +General Terms and Conditions | {{ block.super }} +{% endblock %} + {% block content %}

    Terms and Conditions for BornHack IvS’ sale and delivery of (i) merchandise (ii) tickets and/or (iii) accommodation

    diff --git a/bornhack/templates/legal/privacy_policy.html b/bornhack/templates/legal/privacy_policy.html index 579c48c8..10762aba 100644 --- a/bornhack/templates/legal/privacy_policy.html +++ b/bornhack/templates/legal/privacy_policy.html @@ -1,5 +1,9 @@ {% extends 'base.html' %} +{% block title %} +Privacy Policy | {{ block.super }} +{% endblock %} + {% block content %}

    Privacy and cookie policy

    diff --git a/bornhack/templates/speakers.html b/bornhack/templates/speakers.html index ce9f8d3f..74f17b2e 100644 --- a/bornhack/templates/speakers.html +++ b/bornhack/templates/speakers.html @@ -1,5 +1,9 @@ {% extends 'base.html' %} +{% block title %} +Call for Speakers | {{ block.super }} +{% endblock %} + {% block content %}

    BornHack 2016: Call for Speakers

    diff --git a/bornhack/templates/sponsors.html b/bornhack/templates/sponsors.html index e7271123..91f270ed 100644 --- a/bornhack/templates/sponsors.html +++ b/bornhack/templates/sponsors.html @@ -1,5 +1,9 @@ {% extends 'base.html' %} +{% block title %} +Call for Sponsors | {{ block.super }} +{% endblock %} + {% block content %}

    Sponsoring

    diff --git a/bornhack/urls.py b/bornhack/urls.py index d291f52a..5cdd323b 100644 --- a/bornhack/urls.py +++ b/bornhack/urls.py @@ -74,6 +74,10 @@ urlpatterns = [ r'^news/', include('news.urls', namespace='news') ), + url( + r'^villages/', + include('villages.urls', namespace='villages') + ), url(r'^accounts/', include('allauth.urls')), url(r'^admin/', include(admin.site.urls)), ] diff --git a/news/templates/news_detail.html b/news/templates/news_detail.html index 15eced7e..8cc0c953 100644 --- a/news/templates/news_detail.html +++ b/news/templates/news_detail.html @@ -1,6 +1,10 @@ {% extends 'base.html' %} {% load commonmark %} +{% block title %} +{{ news_item.title }} | {{ block.super }} +{% endblock %} + {% block content %}
    {% if not_public %} diff --git a/news/templates/news_index.html b/news/templates/news_index.html index 18469c3c..30ee0995 100644 --- a/news/templates/news_index.html +++ b/news/templates/news_index.html @@ -1,6 +1,10 @@ {% extends 'base.html' %} {% load commonmark %} +{% block title %} +News | {{ block.super }} +{% endblock %} + {% block content %} {% for item in news_items %}
    diff --git a/shop/templates/product_detail.html b/shop/templates/product_detail.html index 8841123e..a5408361 100644 --- a/shop/templates/product_detail.html +++ b/shop/templates/product_detail.html @@ -3,6 +3,10 @@ {% load commonmark %} {% load shop_tags %} +{% block title %} +{{ product.name }} | {{ block.super }} +{% endblock %} + {% block shop_content %}
    diff --git a/shop/templates/shop_base.html b/shop/templates/shop_base.html index 71cf3a6e..9f213393 100644 --- a/shop/templates/shop_base.html +++ b/shop/templates/shop_base.html @@ -19,7 +19,21 @@ {% if has_tickets %}
  • Tickets
  • {% endif %} -
  • Orders
  • +
  • Orders
  • +
  • + + {% if current_order and current_order.get_number_of_items %} + + {% endif %} + + + + {{ current_order.get_number_of_items|default:0 }} + + {% if current_order and current_order.get_number_of_items %} + + {% endif %} +
  • {% endif %}
    diff --git a/shop/templates/shop_index.html b/shop/templates/shop_index.html index 45df7340..fc0c8ad7 100644 --- a/shop/templates/shop_index.html +++ b/shop/templates/shop_index.html @@ -2,6 +2,10 @@ {% load bootstrap3 %} {% load shop_tags %} +{% block title %} +Shop | {{ block.super }} +{% endblock %} + {% block shop_content %}
    diff --git a/villages/__init__.py b/villages/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/villages/admin.py b/villages/admin.py new file mode 100644 index 00000000..e752aaea --- /dev/null +++ b/villages/admin.py @@ -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', + ] diff --git a/villages/apps.py b/villages/apps.py new file mode 100644 index 00000000..fbf4978a --- /dev/null +++ b/villages/apps.py @@ -0,0 +1,7 @@ +from __future__ import unicode_literals + +from django.apps import AppConfig + + +class VillagesConfig(AppConfig): + name = 'villages' diff --git a/villages/managers.py b/villages/managers.py new file mode 100644 index 00000000..9966196d --- /dev/null +++ b/villages/managers.py @@ -0,0 +1,9 @@ +from django.db.models import QuerySet + + +class VillageQuerySet(QuerySet): + + def not_deleted(self): + return self.filter( + deleted=False + ) diff --git a/villages/migrations/0001_initial.py b/villages/migrations/0001_initial.py new file mode 100644 index 00000000..47c87f10 --- /dev/null +++ b/villages/migrations/0001_initial.py @@ -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'], + }, + ), + ] diff --git a/villages/migrations/0002_auto_20160705_2154.py b/villages/migrations/0002_auto_20160705_2154.py new file mode 100644 index 00000000..020819be --- /dev/null +++ b/villages/migrations/0002_auto_20160705_2154.py @@ -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'), + ), + ] diff --git a/villages/migrations/0003_auto_20160705_2159.py b/villages/migrations/0003_auto_20160705_2159.py new file mode 100644 index 00000000..af9a136c --- /dev/null +++ b/villages/migrations/0003_auto_20160705_2159.py @@ -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'), + ), + ] diff --git a/villages/migrations/0004_village_deleted.py b/villages/migrations/0004_village_deleted.py new file mode 100644 index 00000000..3c1e5b3a --- /dev/null +++ b/villages/migrations/0004_village_deleted.py @@ -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), + ), + ] diff --git a/villages/migrations/__init__.py b/villages/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/villages/models.py b/villages/models.py new file mode 100644 index 00000000..5104b915 --- /dev/null +++ b/villages/models.py @@ -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() diff --git a/villages/templates/village_confirm_delete.html b/villages/templates/village_confirm_delete.html new file mode 100644 index 00000000..d4fd816c --- /dev/null +++ b/villages/templates/village_confirm_delete.html @@ -0,0 +1,13 @@ +{% extends 'base.html' %} +{% load commonmark %} + +{% block content %} + +
    {% csrf_token %} +

    Are you sure you want to delete the village "{{ village }}"?

    + +
    +
    + Cancel +
    +{% endblock %} diff --git a/villages/templates/village_detail.html b/villages/templates/village_detail.html new file mode 100644 index 00000000..7ab7a9ed --- /dev/null +++ b/villages/templates/village_detail.html @@ -0,0 +1,16 @@ +{% extends 'base.html' %} +{% load commonmark %} + +{% block content %} + +

    {{ village.name }}

    + +{{ village.description|commonmark }} + +{% if user == village.contact %} +
    +Edit +Delete +{% endif %} + +{% endblock %} diff --git a/villages/templates/village_form.html b/villages/templates/village_form.html new file mode 100644 index 00000000..3b5f8534 --- /dev/null +++ b/villages/templates/village_form.html @@ -0,0 +1,16 @@ +{% extends 'base.html' %} +{% load bootstrap3 %} + +{% block content %} + +
    + {% csrf_token %} + + {% bootstrap_form form %} + +
    + + +
    + +{% endblock %} \ No newline at end of file diff --git a/villages/templates/village_list.html b/villages/templates/village_list.html new file mode 100644 index 00000000..75a3015e --- /dev/null +++ b/villages/templates/village_list.html @@ -0,0 +1,52 @@ +{% extends 'base.html' %} + +{% block content %} + +

    + 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. +

    + +

    + + It is also possible to rent a tent, chairs and tables for villages here. + +

    + +{% if user.is_authenticated %} +Create a village +{% endif %} + +
    + + + + + + + + + + + {% for village in villages %} + + + + + + {% endfor %} + +
    NameDescriptionPublic
    + + {{ village.name }} + + + {{ village.description|truncatewords:15 }} + + +
    + +{% endblock %} \ No newline at end of file diff --git a/villages/tests.py b/villages/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/villages/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/villages/urls.py b/villages/urls.py new file mode 100644 index 00000000..7a5d473f --- /dev/null +++ b/villages/urls.py @@ -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[-_\w+]+)/delete/$', VillageDeleteView.as_view(), name='delete'), + url(r'(?P[-_\w+]+)/edit/$', VillageUpdateView.as_view(), name='update'), + url(r'(?P[-_\w+]+)/$', VillageDetailView.as_view(), name='detail'), +] diff --git a/villages/views.py b/villages/views.py new file mode 100644 index 00000000..2bde344a --- /dev/null +++ b/villages/views.py @@ -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'