Merge branch 'master' into feature/speakers-app
This commit is contained in:
commit
4381dbd2bd
|
@ -29,6 +29,7 @@ INSTALLED_APPS = [
|
||||||
'shop',
|
'shop',
|
||||||
'news',
|
'news',
|
||||||
'utils',
|
'utils',
|
||||||
|
'villages',
|
||||||
|
|
||||||
'allauth',
|
'allauth',
|
||||||
'allauth.account',
|
'allauth.account',
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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)),
|
||||||
]
|
]
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -9,7 +9,7 @@ admin.site.register(models.CoinifyAPICallback)
|
||||||
admin.site.register(models.Invoice)
|
admin.site.register(models.Invoice)
|
||||||
admin.site.register(models.CreditNote)
|
admin.site.register(models.CreditNote)
|
||||||
admin.site.register(models.Ticket)
|
admin.site.register(models.Ticket)
|
||||||
|
admin.site.register(models.CustomOrder)
|
||||||
|
|
||||||
@admin.register(models.ProductCategory)
|
@admin.register(models.ProductCategory)
|
||||||
class ProductCategoryAdmin(admin.ModelAdmin):
|
class ProductCategoryAdmin(admin.ModelAdmin):
|
||||||
|
@ -41,6 +41,7 @@ class TicketInline(admin.TabularInline):
|
||||||
@admin.register(models.Order)
|
@admin.register(models.Order)
|
||||||
class OrderAdmin(admin.ModelAdmin):
|
class OrderAdmin(admin.ModelAdmin):
|
||||||
change_form_template = 'admin/change_order_form.html'
|
change_form_template = 'admin/change_order_form.html'
|
||||||
|
readonly_fields = ('paid',)
|
||||||
|
|
||||||
def get_email(self, obj):
|
def get_email(self, obj):
|
||||||
return obj.user.email
|
return obj.user.email
|
||||||
|
|
|
@ -4,7 +4,7 @@ from django.conf import settings
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from shop.pdf import generate_pdf_letter
|
from shop.pdf import generate_pdf_letter
|
||||||
from shop.email import send_invoice_email, send_creditnote_email
|
from shop.email import send_invoice_email, send_creditnote_email
|
||||||
from shop.models import Order, Invoice, CreditNote
|
from shop.models import Order, CustomOrder, Invoice, CreditNote
|
||||||
from time import sleep
|
from time import sleep
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
|
@ -19,11 +19,17 @@ class Command(BaseCommand):
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
self.output('Invoice worker running...')
|
self.output('Invoice worker running...')
|
||||||
while True:
|
while True:
|
||||||
# check if we need to generate any invoices
|
# check if we need to generate any invoices for shop orders
|
||||||
for order in Order.objects.filter(paid=True, invoice__isnull=True):
|
for order in Order.objects.filter(paid=True, invoice__isnull=True):
|
||||||
# generate invoice for this Order
|
# generate invoice for this Order
|
||||||
Invoice.objects.create(order=order)
|
Invoice.objects.create(order=order)
|
||||||
self.output('Generated Invoice object for order %s' % order)
|
self.output('Generated Invoice object for %s' % order)
|
||||||
|
|
||||||
|
# check if we need to generate any invoices for custom orders
|
||||||
|
for customorder in CustomOrder.objects.filter(invoice__isnull=True):
|
||||||
|
# generate invoice for this CustomOrder
|
||||||
|
Invoice.objects.create(customorder=customorder)
|
||||||
|
self.output('Generated Invoice object for %s' % customorder)
|
||||||
|
|
||||||
# check if we need to generate any pdf invoices
|
# check if we need to generate any pdf invoices
|
||||||
for invoice in Invoice.objects.filter(pdf=''):
|
for invoice in Invoice.objects.filter(pdf=''):
|
||||||
|
@ -34,9 +40,13 @@ class Command(BaseCommand):
|
||||||
|
|
||||||
# generate the pdf
|
# generate the pdf
|
||||||
try:
|
try:
|
||||||
|
if invoice.customorder:
|
||||||
|
template='pdf/custominvoice.html'
|
||||||
|
else:
|
||||||
|
template='pdf/invoice.html'
|
||||||
pdffile = generate_pdf_letter(
|
pdffile = generate_pdf_letter(
|
||||||
filename=invoice.filename,
|
filename=invoice.filename,
|
||||||
template='pdf/invoice.html',
|
template=template,
|
||||||
formatdict=formatdict,
|
formatdict=formatdict,
|
||||||
)
|
)
|
||||||
self.output('Generated pdf for invoice %s' % invoice)
|
self.output('Generated pdf for invoice %s' % invoice)
|
||||||
|
@ -54,8 +64,8 @@ class Command(BaseCommand):
|
||||||
invoice.save()
|
invoice.save()
|
||||||
|
|
||||||
###############################################################
|
###############################################################
|
||||||
# check if we need to send out any invoices (only where pdf has been generated)
|
# check if we need to send out any invoices (only for shop orders, and only where pdf has been generated)
|
||||||
for invoice in Invoice.objects.filter(sent_to_customer=False).exclude(pdf=''):
|
for invoice in Invoice.objects.filter(order__isnull=False, sent_to_customer=False).exclude(pdf=''):
|
||||||
# send the email
|
# send the email
|
||||||
if send_invoice_email(invoice=invoice):
|
if send_invoice_email(invoice=invoice):
|
||||||
self.output('OK: Invoice email sent to %s' % invoice.order.user.email)
|
self.output('OK: Invoice email sent to %s' % invoice.order.user.email)
|
||||||
|
|
77
shop/migrations/0027_auto_20160712_2036.py
Normal file
77
shop/migrations/0027_auto_20160712_2036.py
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.9.2 on 2016-07-12 20:36
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('camps', '0005_auto_20160510_2011'),
|
||||||
|
('shop', '0026_order_refunded'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='CustomOrder',
|
||||||
|
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)),
|
||||||
|
('text', models.TextField()),
|
||||||
|
('amount', models.IntegerField(help_text='Amount of this custom order (in DKK, including VAT).')),
|
||||||
|
('paid', models.BooleanField(default=False, help_text='Whether this custom order has been paid.', verbose_name='Paid?')),
|
||||||
|
('camp', models.ForeignKey(help_text='The camp this custom order is for.', on_delete=django.db.models.deletion.CASCADE, to='camps.Camp', verbose_name='Camp')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='creditnote',
|
||||||
|
options={'ordering': ['-created']},
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='creditnote',
|
||||||
|
name='paid',
|
||||||
|
field=models.BooleanField(default=False, help_text='Whether the amount in this creditnote has been paid back to the customer.', verbose_name='Paid?'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='invoice',
|
||||||
|
name='order',
|
||||||
|
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='shop.Order'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='order',
|
||||||
|
name='camp',
|
||||||
|
field=models.ForeignKey(help_text='The camp this shop order is for.', on_delete=django.db.models.deletion.CASCADE, to='camps.Camp', verbose_name='Camp'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='order',
|
||||||
|
name='open',
|
||||||
|
field=models.NullBooleanField(default=True, help_text='Whether this shop order is open or not. "None" means closed.', verbose_name='Open?'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='order',
|
||||||
|
name='paid',
|
||||||
|
field=models.BooleanField(default=False, help_text='Whether this shop order has been paid.', verbose_name='Paid?'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='order',
|
||||||
|
name='user',
|
||||||
|
field=models.ForeignKey(help_text='The user this shop order belongs to.', on_delete=django.db.models.deletion.CASCADE, related_name='orders', to=settings.AUTH_USER_MODEL, verbose_name='User'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='product',
|
||||||
|
name='price',
|
||||||
|
field=models.IntegerField(help_text='Price of the product (in DKK, including VAT).'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='invoice',
|
||||||
|
name='customorder',
|
||||||
|
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='shop.CustomOrder'),
|
||||||
|
),
|
||||||
|
]
|
26
shop/migrations/0028_auto_20160712_2119.py
Normal file
26
shop/migrations/0028_auto_20160712_2119.py
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.9.2 on 2016-07-12 21:19
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('shop', '0027_auto_20160712_2036'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='customorder',
|
||||||
|
name='customer',
|
||||||
|
field=models.TextField(default='', help_text='The customer info for this order, use <br> for newlines'),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='customorder',
|
||||||
|
name='text',
|
||||||
|
field=models.TextField(help_text='The invoice text'),
|
||||||
|
),
|
||||||
|
]
|
20
shop/migrations/0029_auto_20160712_2133.py
Normal file
20
shop/migrations/0029_auto_20160712_2133.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.9.2 on 2016-07-12 21:33
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('shop', '0028_auto_20160712_2119'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='customorder',
|
||||||
|
name='customer',
|
||||||
|
field=models.TextField(help_text='The customer info for this order'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -15,8 +15,40 @@ from decimal import Decimal
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
|
|
||||||
class Order(CreatedUpdatedModel):
|
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')
|
||||||
|
)
|
||||||
|
|
||||||
|
customer = models.TextField(
|
||||||
|
help_text=_('The customer info for this order')
|
||||||
|
)
|
||||||
|
|
||||||
|
amount = models.IntegerField(
|
||||||
|
help_text=_('Amount of this custom order (in DKK, including VAT).')
|
||||||
|
)
|
||||||
|
|
||||||
|
paid = models.BooleanField(
|
||||||
|
verbose_name=_('Paid?'),
|
||||||
|
help_text=_('Whether this custom order has been paid.'),
|
||||||
|
default=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return 'custom order id #%s' % self.pk
|
||||||
|
|
||||||
|
@property
|
||||||
|
def vat(self):
|
||||||
|
return Decimal(self.amount*Decimal(0.2))
|
||||||
|
|
||||||
|
|
||||||
|
class Order(CreatedUpdatedModel):
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = ('user', 'open')
|
unique_together = ('user', 'open')
|
||||||
ordering = ['-created']
|
ordering = ['-created']
|
||||||
|
@ -29,26 +61,26 @@ class Order(CreatedUpdatedModel):
|
||||||
user = models.ForeignKey(
|
user = models.ForeignKey(
|
||||||
'auth.User',
|
'auth.User',
|
||||||
verbose_name=_('User'),
|
verbose_name=_('User'),
|
||||||
help_text=_('The user this order belongs to.'),
|
help_text=_('The user this shop order belongs to.'),
|
||||||
related_name='orders',
|
related_name='orders',
|
||||||
)
|
)
|
||||||
|
|
||||||
paid = models.BooleanField(
|
paid = models.BooleanField(
|
||||||
verbose_name=_('Paid?'),
|
verbose_name=_('Paid?'),
|
||||||
help_text=_('Whether this order has been paid.'),
|
help_text=_('Whether this shop order has been paid.'),
|
||||||
default=False,
|
default=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
open = models.NullBooleanField(
|
open = models.NullBooleanField(
|
||||||
verbose_name=_('Open?'),
|
verbose_name=_('Open?'),
|
||||||
help_text=_('Whether this order is open or not. "None" means closed.'),
|
help_text=_('Whether this shop order is open or not. "None" means closed.'),
|
||||||
default=True,
|
default=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
camp = models.ForeignKey(
|
camp = models.ForeignKey(
|
||||||
'camps.Camp',
|
'camps.Camp',
|
||||||
verbose_name=_('Camp'),
|
verbose_name=_('Camp'),
|
||||||
help_text=_('The camp this order is for.'),
|
help_text=_('The camp this shop order is for.'),
|
||||||
)
|
)
|
||||||
|
|
||||||
CREDIT_CARD = 'credit_card'
|
CREDIT_CARD = 'credit_card'
|
||||||
|
@ -84,7 +116,7 @@ class Order(CreatedUpdatedModel):
|
||||||
objects = OrderQuerySet.as_manager()
|
objects = OrderQuerySet.as_manager()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return 'order id #%s' % self.pk
|
return 'shop order id #%s' % self.pk
|
||||||
|
|
||||||
def get_number_of_items(self):
|
def get_number_of_items(self):
|
||||||
return self.products.aggregate(
|
return self.products.aggregate(
|
||||||
|
@ -225,7 +257,7 @@ class Product(CreatedUpdatedModel, UUIDModel):
|
||||||
slug = models.SlugField()
|
slug = models.SlugField()
|
||||||
|
|
||||||
price = models.IntegerField(
|
price = models.IntegerField(
|
||||||
help_text=_('Price of the product (in DKK).')
|
help_text=_('Price of the product (in DKK, including VAT).')
|
||||||
)
|
)
|
||||||
|
|
||||||
description = models.TextField()
|
description = models.TextField()
|
||||||
|
@ -326,19 +358,29 @@ class CreditNote(CreatedUpdatedModel):
|
||||||
|
|
||||||
|
|
||||||
class Invoice(CreatedUpdatedModel):
|
class Invoice(CreatedUpdatedModel):
|
||||||
order = models.OneToOneField('shop.Order')
|
order = models.OneToOneField('shop.Order', null=True, blank=True)
|
||||||
|
customorder = models.OneToOneField('shop.CustomOrder', null=True, blank=True)
|
||||||
pdf = models.FileField(null=True, blank=True, upload_to='invoices/')
|
pdf = models.FileField(null=True, blank=True, upload_to='invoices/')
|
||||||
sent_to_customer = models.BooleanField(default=False)
|
sent_to_customer = models.BooleanField(default=False)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return 'invoice#%s - order %s - %s - total %s DKK (sent to %s: %s)' % (
|
if self.order:
|
||||||
self.id,
|
return 'invoice#%s - shop order %s - %s - total %s DKK (sent to %s: %s)' % (
|
||||||
self.order.id,
|
self.id,
|
||||||
self.order.created,
|
self.order.id,
|
||||||
self.order.total,
|
self.order.created,
|
||||||
self.order.user.email,
|
self.order.total,
|
||||||
self.sent_to_customer,
|
self.order.user.email,
|
||||||
)
|
self.sent_to_customer,
|
||||||
|
)
|
||||||
|
elif self.customorder:
|
||||||
|
return 'invoice#%s - custom order %s - %s - amount %s DKK (customer: %s)' % (
|
||||||
|
self.id,
|
||||||
|
self.customorder.id,
|
||||||
|
self.customorder.created,
|
||||||
|
self.customorder.amount,
|
||||||
|
self.customorder.customer,
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def filename(self):
|
def filename(self):
|
||||||
|
|
51
shop/templates/pdf/custominvoice.html
Normal file
51
shop/templates/pdf/custominvoice.html
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
{% load static from staticfiles %}
|
||||||
|
{% load shop_tags %}
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||||
|
|
||||||
|
<table style="width:100%;">
|
||||||
|
<tr>
|
||||||
|
<td style="width: 75%;"> </td>
|
||||||
|
<td>
|
||||||
|
<h3>
|
||||||
|
{{ invoice.created|date:"b jS, Y" }}<br>
|
||||||
|
Custom Order #{{ invoice.customorder.pk }}<br>
|
||||||
|
Invoice #{{ invoice.pk }}
|
||||||
|
</h3>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h2>CUSTOMER</h2>
|
||||||
|
<p class="lead">{{ invoice.customorder.customer|linebreaks }}</p>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<h2>INVOICE</h2>
|
||||||
|
<table style="width:90%">
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<b>Text
|
||||||
|
<td align="center" style="width: 20%">
|
||||||
|
<b>Price
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
{{ invoice.customorder.text|linebreaks }}
|
||||||
|
<td align="right">
|
||||||
|
{{ invoice.customorder.amount|currency }}
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td align="right">
|
||||||
|
<strong>Danish VAT (25%)</strong>
|
||||||
|
<td align="right">
|
||||||
|
{{ invoice.customorder.vat|currency }}
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td align="right">
|
||||||
|
<strong>Total</strong>
|
||||||
|
<td align="right">
|
||||||
|
{{ invoice.customorder.amount|currency }}
|
||||||
|
</table>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<p class="lead">
|
||||||
|
Payment should be made by bank transfer to our account in Arbejdernes Landsbank reg. 5371 account no. 0244504 within two weeks from {{ invoice.created|date:"b jS, Y" }} please. Thank you!
|
||||||
|
</p>
|
|
@ -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">
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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
0
villages/__init__.py
Normal file
17
villages/admin.py
Normal file
17
villages/admin.py
Normal 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
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'
|
9
villages/managers.py
Normal file
9
villages/managers.py
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
from django.db.models import QuerySet
|
||||||
|
|
||||||
|
|
||||||
|
class VillageQuerySet(QuerySet):
|
||||||
|
|
||||||
|
def not_deleted(self):
|
||||||
|
return self.filter(
|
||||||
|
deleted=False
|
||||||
|
)
|
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'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
24
villages/migrations/0002_auto_20160705_2154.py
Normal file
24
villages/migrations/0002_auto_20160705_2154.py
Normal 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'),
|
||||||
|
),
|
||||||
|
]
|
20
villages/migrations/0003_auto_20160705_2159.py
Normal file
20
villages/migrations/0003_auto_20160705_2159.py
Normal 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'),
|
||||||
|
),
|
||||||
|
]
|
20
villages/migrations/0004_village_deleted.py
Normal file
20
villages/migrations/0004_village_deleted.py
Normal 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),
|
||||||
|
),
|
||||||
|
]
|
20
villages/migrations/0005_auto_20160712_2036.py
Normal file
20
villages/migrations/0005_auto_20160712_2036.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.9.2 on 2016-07-12 20:36
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('villages', '0004_village_deleted'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='village',
|
||||||
|
name='description',
|
||||||
|
field=models.TextField(help_text='A descriptive text about your village. Markdown is supported.'),
|
||||||
|
),
|
||||||
|
]
|
0
villages/migrations/__init__.py
Normal file
0
villages/migrations/__init__.py
Normal file
72
villages/models.py
Normal file
72
villages/models.py
Normal 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()
|
13
villages/templates/village_confirm_delete.html
Normal file
13
villages/templates/village_confirm_delete.html
Normal 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 %}
|
20
villages/templates/village_detail.html
Normal file
20
villages/templates/village_detail.html
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load commonmark %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
Village: {{ village.name }} | {{ block.super }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% 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 %}
|
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 %}
|
56
villages/templates/village_list.html
Normal file
56
villages/templates/village_list.html
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
Villages | {{ block.super }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% 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:50 }}
|
||||||
|
</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
3
villages/tests.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
10
villages/urls.py
Normal file
10
villages/urls.py
Normal 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
50
villages/views.py
Normal 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'
|
Loading…
Reference in a new issue