Initial villages feature
This commit is contained in:
parent
fe0bc14ac1
commit
8786b5af88
|
@ -29,6 +29,7 @@ INSTALLED_APPS = [
|
||||||
'shop',
|
'shop',
|
||||||
'news',
|
'news',
|
||||||
'utils',
|
'utils',
|
||||||
|
'villages',
|
||||||
|
|
||||||
'allauth',
|
'allauth',
|
||||||
'allauth.account',
|
'allauth.account',
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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)),
|
||||||
]
|
]
|
||||||
|
|
|
@ -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>
|
||||||
|
|
0
villages/__init__.py
Normal file
0
villages/__init__.py
Normal file
8
villages/admin.py
Normal file
8
villages/admin.py
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from .models import Village
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Village)
|
||||||
|
class VillageAdmin(admin.ModelAdmin):
|
||||||
|
pass
|
7
villages/apps.py
Normal file
7
villages/apps.py
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class VillagesConfig(AppConfig):
|
||||||
|
name = 'villages'
|
38
villages/migrations/0001_initial.py
Normal file
38
villages/migrations/0001_initial.py
Normal 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'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
0
villages/migrations/__init__.py
Normal file
0
villages/migrations/__init__.py
Normal file
58
villages/models.py
Normal file
58
villages/models.py
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
open = models.BooleanField(
|
||||||
|
default=False,
|
||||||
|
help_text='Is this village open for others to join?'
|
||||||
|
)
|
||||||
|
|
||||||
|
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)
|
16
villages/templates/village_detail.html
Normal file
16
villages/templates/village_detail.html
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load commonmark %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<h3>{{ village.name }}</h3>
|
||||||
|
|
||||||
|
{{ village.description|commonmark }}
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
{% if user in village.users.all %}
|
||||||
|
<a href="{% url 'villages:update' slug=village.slug %}">Edit</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endblock %}
|
16
villages/templates/village_form.html
Normal file
16
villages/templates/village_form.html
Normal 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 %}
|
38
villages/templates/village_list.html
Normal file
38
villages/templates/village_list.html
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
{% 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>
|
||||||
|
|
||||||
|
{% 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>Price</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>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{% endblock %}
|
3
villages/tests.py
Normal file
3
villages/tests.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
9
villages/urls.py
Normal file
9
villages/urls.py
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
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+]+)/edit/$', VillageUpdateView.as_view(), name='update'),
|
||||||
|
url(r'(?P<slug>[-_\w+]+)/$', VillageDetailView.as_view(), name='detail'),
|
||||||
|
]
|
40
villages/views.py
Normal file
40
villages/views.py
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
from django.core.urlresolvers import reverse_lazy
|
||||||
|
from django.http import HttpResponseRedirect
|
||||||
|
from django.views.generic import ListView, DetailView, CreateView, UpdateView
|
||||||
|
from .models import (
|
||||||
|
Village,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class VillageListView(ListView):
|
||||||
|
model = Village
|
||||||
|
template_name = 'village_list.html'
|
||||||
|
context_object_name = 'villages'
|
||||||
|
|
||||||
|
|
||||||
|
class VillageDetailView(DetailView):
|
||||||
|
model = Village
|
||||||
|
template_name = 'village_detail.html'
|
||||||
|
context_object_name = 'village'
|
||||||
|
|
||||||
|
|
||||||
|
class VillageCreateView(CreateView):
|
||||||
|
model = Village
|
||||||
|
template_name = 'village_form.html'
|
||||||
|
fields = ['name', 'description', 'open']
|
||||||
|
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
|
||||||
|
template_name = 'village_form.html'
|
||||||
|
fields = ['name', 'description', 'open']
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
return self.get_object().get_absolute_url()
|
Loading…
Reference in a new issue