Commit 86cd0c13 authored by Joost Rijneveld's avatar Joost Rijneveld Committed by Sébastiaan Versteeg

Add pizza app with basic functionality

Notable TODO is adding messages for end-users on actions
parent 5cb0c87f
from django.contrib import admin
from .models import PizzaEvent, Order, Product
from django.utils.html import format_html
from django.utils.translation import ugettext_lazy as _
from django.core.urlresolvers import reverse
admin.site.register(Product)
@admin.register(PizzaEvent)
class PizzaEventAdmin(admin.ModelAdmin):
list_display = ('title', 'orders')
def orders(self, obj):
return format_html(_('<strong><a href="{link}">Orders</a></strong>'),
link=reverse('pizzas:orders',
kwargs={'event_pk': obj.pk}))
@admin.register(Order)
class OrderAdmin(admin.ModelAdmin):
list_display = ('pizza_event', 'member_name', 'product', 'paid')
from django.apps import AppConfig
class PizzasConfig(AppConfig):
name = 'pizzas'
from django.forms import ModelForm
from .models import Order
class AddOrderForm(ModelForm):
class Meta:
model = Order
fields = ['name', 'product']
This diff was suppressed by a .gitattributes entry.
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-08-13 17:41+0200\n"
"PO-Revision-Date: 2016-08-13 17:56+0100\n"
"Last-Translator: Joost Rijneveld <joost@joostrijneveld.nl>\n"
"Language-Team: \n"
"Language: nl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 1.6.10\n"
#: models.py:10
msgid "Order from"
msgstr "Bestel vanaf"
#: models.py:11
msgid "Order until"
msgstr "Bestel tot"
#: models.py:39 models.py:40 models.py:81 models.py:82
msgid "Either specify a member or a name"
msgstr "Specificeer een lid of vul een naam in"
#: models.py:70
msgid "Use this for non-members"
msgstr "Vul dit in voor niet-leden"
#: templates/pizzas/index.html:5
msgid "Pizzas"
msgstr "Pizza's"
#: templates/pizzas/index.html:11
#, python-format
msgid "Order food for %(event.title)s"
msgstr "Bestel eten voor %(event.title)s"
#: templates/pizzas/index.html:15
#, python-format
msgid "It will be possible to order from %(start)s."
msgstr "Je kan bestellen vanaf %(start)s."
#: templates/pizzas/index.html:20
#, python-format
msgid "It was possible to order until %(end)s."
msgstr "Je kon bestellen tot %(end)s."
#: templates/pizzas/index.html:24
#, python-format
msgid "It is possible to order until %(end)s."
msgstr "Je kan bestellen tot %(end)s."
#: templates/pizzas/index.html:29
msgid "The order has not yet been paid for."
msgstr "De bestelling is nog niet betaald."
#: templates/pizzas/index.html:31
msgid "The order has been paid for."
msgstr "De bestelling is betaald."
#: templates/pizzas/index.html:33
msgid "Current order"
msgstr "Huidige bestelling"
#: templates/pizzas/index.html:37 templates/pizzas/index.html:82
msgid "Name"
msgstr "Naam"
#: templates/pizzas/index.html:38 templates/pizzas/index.html:83
msgid "Description"
msgstr "Omschrijving"
#: templates/pizzas/index.html:39 templates/pizzas/index.html:84
msgid "Price"
msgstr "Prijs"
#: templates/pizzas/index.html:40
msgid "Cancel"
msgstr "Annuleren"
#: templates/pizzas/index.html:73
msgid "Changing your order"
msgstr "Bestelling bewerken"
#: templates/pizzas/index.html:76
msgid "You did not place an order."
msgstr "Je hebt geen bestelling geplaatst."
#: templates/pizzas/index.html:85
msgid "Modify"
msgstr "Bewerken"
#: templates/pizzas/index.html:85
msgid "Order"
msgstr "Bestellen"
#: templates/pizzas/index.html:107
msgid "There is no current event for which you can order food"
msgstr "Er is nu geen evenement waar je eten voor kunt bestellen"
# -*- coding: utf-8 -*-
# Generated by Django 1.10 on 2016-08-24 21:31
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
('members', '0004_auto_20160805_1435'),
('events', '0003_remove_event_registration_required'),
]
operations = [
migrations.CreateModel(
name='Order',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('paid', models.BooleanField(default=False)),
('name', models.CharField(blank=True, help_text='Use this for non-members', max_length=50, null=True)),
('member', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='members.Member')),
],
),
migrations.CreateModel(
name='PizzaEvent',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('start', models.DateTimeField(verbose_name='Order from')),
('end', models.DateTimeField(verbose_name='Order until')),
('event', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='events.Event')),
],
),
migrations.CreateModel(
name='Product',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=50)),
('price', models.DecimalField(decimal_places=2, max_digits=5)),
('available', models.BooleanField(default=True)),
('description_en', models.TextField(verbose_name='description (EN)')),
('description_nl', models.TextField(verbose_name='description (NL)')),
],
),
migrations.AddField(
model_name='order',
name='pizza_event',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='pizzas.PizzaEvent'),
),
migrations.AddField(
model_name='order',
name='product',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='pizzas.Product'),
),
migrations.AlterUniqueTogether(
name='order',
unique_together=set([('pizza_event', 'member')]),
),
]
from django.db import models
from django.utils import timezone
from django.core.exceptions import ValidationError
from utils.translation import MultilingualField, ModelTranslateMeta
from django.utils.translation import ugettext_lazy as _
import events
import members
class PizzaEvent(models.Model):
start = models.DateTimeField(_("Order from"))
end = models.DateTimeField(_("Order until"))
event = models.OneToOneField(events.models.Event, on_delete=models.CASCADE)
@property
def title(self):
return self.event.title
@property
def in_the_future(self):
return self.start > timezone.now()
@property
def just_ended(self):
return (self.end < timezone.now()
and self.end + timezone.timedelta(hours=8) > timezone.now())
@classmethod
def current(cls):
try:
return PizzaEvent.objects.get(
end__gt=timezone.now() - timezone.timedelta(hours=8)
)
except PizzaEvent.DoesNotExist:
return None
def clean(self):
for other in PizzaEvent.objects.all():
if self.start <= other.end and other.start >= self.end:
raise ValidationError({
'start': _('This event cannot overlap with ') + str(other),
'end': _('This event cannot overlap with ') + str(other),
})
def __str__(self):
return 'Pizzas for ' + str(self.event)
class Product(models.Model, metaclass=ModelTranslateMeta):
name = models.CharField(max_length=50)
description = MultilingualField(models.TextField)
price = models.DecimalField(max_digits=5, decimal_places=2)
available = models.BooleanField(default=True)
def __str__(self):
return self.name
class Order(models.Model):
member = models.ForeignKey(
members.models.Member,
on_delete=models.CASCADE,
blank=True,
null=True,
)
paid = models.BooleanField(default=False)
name = models.CharField(
max_length=50,
help_text=_('Use this for non-members'),
null=True,
blank=True,
)
product = models.ForeignKey(Product, on_delete=models.PROTECT)
pizza_event = models.ForeignKey(PizzaEvent, on_delete=models.CASCADE)
def clean(self):
if ((self.member is None and not self.name) or
(self.member and self.name)):
raise ValidationError({
'member': _('Either specify a member or a name'),
'name': _('Either specify a member or a name'),
})
@property
def member_name(self):
if self.member is not None:
return self.member.get_full_name()
return self.name
class Meta:
unique_together = ('pizza_event', 'member')
.pizza-order-form, .activity-form, .paid-form, .collected-form, .delete-form {
margin-bottom: 0px;
}
#button-container {
clear: both;
overflow: auto;
}
#button-container a {
float: right;
}
.paid {
background-color: #8FCC74;
}
.unpaid {
background-color: #E05C50;
}
@media (max-width: 767px) {
#main .main-content {
padding-right: 16px !important;
padding-left: 16px !important;
max-width: none;
}
.main-content .btn, .main-content .btn:hover {
clear: right;
margin-bottom: 4px;
}
}
\ No newline at end of file
/*
A simple, lightweight jQuery plugin for creating sortable tables.
https://github.com/kylefox/jquery-tablesort
Version 0.0.6
*/
$(function(){var t=window.jQuery;t.tablesort=function(e,s){var i=this;this.$table=e,this.$thead=this.$table.find("thead"),this.settings=t.extend({},t.tablesort.defaults,s),this.$sortCells=this.$thead.length>0?this.$thead.find("th:not(.no-sort)"):this.$table.find("th:not(.no-sort)"),this.$sortCells.bind("click.tablesort",function(){i.sort(t(this))}),this.index=null,this.$th=null,this.direction=null},t.tablesort.prototype={sort:function(e,s){var i=new Date,n=this,o=this.$table,a=this.$thead.length>0?o.find("tbody tr"):o.find("tr").has("td"),l=o.find("tr td:nth-of-type("+(e.index()+1)+")"),r=e.data().sortBy,d=[],h=l.map(function(s,i){return r?"function"==typeof r?r(t(e),t(i),n):r:null!=t(this).data().sortValue?t(this).data().sortValue:t(this).text()});0!==h.length&&("asc"!==s&&"desc"!==s?this.direction="asc"===this.direction?"desc":"asc":this.direction=s,s="asc"==this.direction?1:-1,n.$table.trigger("tablesort:start",[n]),n.log("Sorting by "+this.index+" "+this.direction),n.$table.css("display"),setTimeout(function(){n.$sortCells.removeClass(n.settings.asc+" "+n.settings.desc);for(var r=0,c=h.length;c>r;r++)d.push({index:r,cell:l[r],row:a[r],value:h[r]});d.sort(function(t,e){return t.value>e.value?1*s:t.value<e.value?-1*s:0}),t.each(d,function(t,e){o.append(e.row)}),e.addClass(n.settings[n.direction]),n.log("Sort finished in "+((new Date).getTime()-i.getTime())+"ms"),n.$table.trigger("tablesort:complete",[n]),n.$table.css("display")},h.length>2e3?200:10))},log:function(e){(t.tablesort.DEBUG||this.settings.debug)&&console&&console.log&&console.log("[tablesort] "+e)},destroy:function(){return this.$sortCells.unbind("click.tablesort"),this.$table.data("tablesort",null),null}},t.tablesort.DEBUG=!1,t.tablesort.defaults={debug:t.tablesort.DEBUG,asc:"sorted ascending",desc:"sorted descending"},t.fn.tablesort=function(e){var s,i;return this.each(function(){s=t(this),i=s.data("tablesort"),i&&i.destroy(),s.data("tablesort",new t.tablesort(s,e))})}});
\ No newline at end of file
$(function() {
$('table').tablesort();
$('thead th.paid-title, thead th.collected-title').data('sortBy', function (th, td, tablesort) {
return $(td).find('.btn').val();
});
$('input.paid-button').click(function() {
var id = $(this).data('id');
var button = $(this);
$.post(paid_url, {'order': id, 'csrfmiddlewaretoken': csrf_token}, function(data) {
if(data.success === 1) {
button.addClass('btn-style' + (4 - data.paid));
button.removeClass('btn-style' + (3 + data.paid));
button.val(data.paid ? 'Yes' : 'No');
button.blur();
}
else {
alert(data.error);
}
});
});
});
\ No newline at end of file
{% extends "base.html" %}
{% load i18n staticfiles %}
{% block page_title %}{% trans "Pizzas" %}{% endblock %}
{% block css_head %}
{{ block.super }}
<link href="{% static "pizzas/css/style.css" %}" rel="stylesheet" type="text/css">
{% endblock %}
{% block body %}
<style>
.form-group {
margin-bottom: 15px;
}
</style>
<h3>{% blocktrans with title=event.title %}Add order for {{ title }}{% endblocktrans %}</h3>
<form class="form-horizontal" method="post" action="{% url 'pizzas:add-order' event.pk %}">
{% csrf_token %}
<div class="form-group {% if form.name.errors %}has-error{% endif %}">
<label for="{{ form.name.auto_id }}" class="control-label">{{ form.name.label }}</label>
<div class="controls">
{{ form.name }}
{% for error in form.name.errors %}
<span class="help-block">{{ error }}</span>
{% endfor %}
</div>
</div>
<div class="form-group {% if form.product.errors %}has-error{% endif %}">
<label for="{{ form.product.auto_id }}" class="control-label">{{ form.product.label }}</label>
<div class="controls">
{{ form.product }}
</div>
</div>
<div class="form-group">
<input type="submit" class="btn btn-style1" value="{% trans 'Add' %}">
</div>
</form>
<div class="text-right">
{% url 'admin:pizzas_order_add' as url_pizzas_order_add %}
{% blocktrans%}A more generic version of this form is<br>also available in the <a href="{{ url_pizzas_order_add }}">administration back-end</a>.{% endblocktrans %}
</div>
{% endblock %}
{% extends "base.html" %}
{% load i18n staticfiles %}
{% block page_title %}{% trans "Pizzas" %}{% endblock %}
{% block css_head %}
{{ block.super }}
<link href="{% static "pizzas/css/style.css" %}" rel="stylesheet" type="text/css">
{% endblock %}
{% block body %}
{% if event %}
{% if event.just_ended and order %}
<center>
<span style="font-weight: bolder; font-size: 40px">
{{ order.product.name }}
</span>
<br>
<br>
<span style="font-size: 24px;">
{{ member.get_full_name }}
</span>
</center>
{% endif %}
<h1>
{% blocktrans %}Order food for {{ event.title }}{% endblocktrans %}
</h1>
{% else %}
<h1>
{% trans "There is no current event for which you can order food" %}
</h1>
{% endif %}
<div id="button-container">
{% if perms.pizzas.change_pizzaevent %}
<a href="{% url 'admin:pizzas_pizzaevent_changelist' %}" class="btn btn-primary pull-right">{% trans "All events" %}</a>
{% endif %}
{% if perms.pizzas.change_order and event %}
<a href="{% url 'pizzas:orders' event.pk %}" class="btn btn-primary pull-right">{% trans "All orders" %}</a>
{% endif %}
<br><br>
</div>
{% if event %}
{% if event.in_the_future %}
<h2>
{% blocktrans with start=event.start|date:"Y-m-d H:m:s" %}It will be possible to order from {{ start }}.{% endblocktrans %}
</h2>
{% else %}
{% if event.just_ended %}
<h2>
{% blocktrans with end=event.end|date:"Y-m-d H:m:s" %}It was possible to order until {{ end }}.{% endblocktrans %}
</h2>
{% else %}
<h2>
{% blocktrans with end=event.end|date:"Y-m-d H:m:s" %}It is possible to order until {{ end }}.{% endblocktrans %}
</h2>
{% endif %}
{% if order %}
{% if order.paid %}
<div class="alert alert-success">{% trans "The order has been paid for." %}</div>
{% else %}
<div class="alert alert-error">{% trans "The order has not yet been paid for." %}</div>
{% endif %}
<h4>{% trans "Current order" %}</h4>
<div style="overflow-x: auto;">
<table class="table table-striped table-bordered table-hover">
<tr>
<th>{% trans "Name" %}</th>
<th>{% trans "Description" %}</th>
<th>{% trans "Price" %}</th>
<th>{% trans "Cancel" %}</th>
</tr>
<tr>
<td>{{ order.product.name }}</td>
<td>{{ order.product.description }}</td>
<td>&euro; {{ order.product.price }}</td>
<td>
{% if event.just_ended %}
{% trans "You can no longer cancel." %}
{% else %}
<form class="form-horizontal pizza-order-form" method="post" action="{% url 'pizzas:cancel-order' %}">
{% csrf_token %}
<input type='hidden' name='order' value='{{ order.pk }}' />
<input type='submit' value='{% trans "Cancel" %}' class='btn btn-style1' onclick="return confirm('{% trans "Are you sure you want to cancel your order?" %}')" />
</form>
{% endif %}
</td>
</tr>
</table>
</div>
{% if event.just_ended %}
<style>
.header-image {
background-color: {{ order.paid|yesno:'#DFF0D8,#E05C50' }} !important;
background-image: none !important;
border-top: 1px solid {{ order.paid|yesno:'#8FCC74,#EED3D7' }} !important;
}
#main {
border-top: 1px solid {{ order.paid|yesno:'8FCC74,EED3D7' }} !important;
}
</style>
{% else %}
<h4>{% trans "Changing your order" %}</h4>
{% endif %}
{% elif event.just_ended %}
{% trans "You did not place an order." %}
{% endif %}
{% if not event.just_ended %}
<div style="overflow-x: auto;">
<table class="category table table-striped table-bordered table-hover">
<tr>
<th>{% trans "Name" %}</th>
<th>{% trans "Description" %}</th>
<th>{% trans "Price" %}</th>
<th>{% if order %}{% trans "Modify" %}{% else %}{% trans "Order" %}{% endif %}</th>
</tr>
{% for product in products %}
<tr>
<td>{{ product.name }}</td>
<td>{{ product.description }}</td>
<td>&euro; {{ product.price }}</td>
<td>
<form class="form-horizontal pizza-order-form" method="post" action="{% url 'pizzas:order' %}">
{% csrf_token %}
<input type='hidden' name='product' value='{{ product.pk }}' />
<input type='submit' class='btn btn-style1' {% if order %}value='{% trans "Modify" %}' onclick="return confirm('{% trans "Are you sure you want to modify your order?" %}')"{% else %}value='{% trans "Order" %}'{% endif %} />
</form>
</td>
</tr>
{% endfor %}
</table>
</div>
{% endif %}
{% endif %}
{% endif %}
{% endblock %}
{% extends "base.html" %}
{% load i18n staticfiles %}
{% block page_title %}{% trans "Pizzas" %}{% endblock %}
{% block css_head %}
{{ block.super }}
<link href="{% static "pizzas/css/style.css" %}" rel="stylesheet" type="text/css">
{% endblock %}
{% block body %}
<h1>
{% blocktrans %}Orders for {{ event.title }}{% endblocktrans %}
</h1>
<div id="button-container">
<a href="{% url 'pizzas:add-order' event.pk %}" class="btn btn-primary pull-right">{% trans "Add a new order" %}</a>
<a href="{% url 'admin:pizzas_pizzaevent_changelist' %}" class="btn btn-primary pull-right">{% trans "All events" %}</a>
<br><br>
</div>
<div style="overflow-x: auto;">
<table class="category table table-striped table-bordered table-hover">
<thead>
<tr>
<th scope="col">{% trans "Name" %}</th>
<th scope="col">{% trans "Product" %}</th>
<th class='paid-title' scope="col">{% trans "Paid" %}</th>
<th class="no-sort"><i class="fa fa-trash"></i></th>
</tr>
</thead>
<tbody>
{% for order in orders %}
<tr>
<td>
{{ order.member_name }}
</td>
<td>
{{ order.product }}
</td>
<td>
<input type="button" class="btn btn-style{{ order.paid|yesno:'3,4' }} paid-button" value="{% if order.paid %}{% trans 'Yes' %}{% else %}{% trans 'No' %}{% endif %}" data-id="{{ order.pk }}">
</td>
<td>
<form class="form-horizontal delete-form" method="post" action="{% url 'pizzas:delete-order' %}">
{% csrf_token %}
<input type='hidden' name='order' value='{{ order.pk }}' />
<button type="submit" class="btn btn-style1 btn" onclick="return confirm('{% trans "Are you sure you want to delete this order?" %}');">
<i class="fa fa-trash"></i>
</button>
</form>
</td>
</tr>
{% empty %}
<tr class="model-events">
<th scope="row">{% trans "Nobody ordered yet" %}</th>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>