Итак, как обстоят дела: после первого рывка я скурпулезено заполнял базу через существующие странички. Иногда когда скрипели зубы - добавлял функциональность. А так могу сказать - сейчас меня более-менее устраивает, все работает как и должно, есть куда развивать систему, следующие шаги ясны. Собственно, вот продолжение хроник, информация восстановлена по change-ам. Все измения фискируются в source-control'е в качестве которого я успешно использую bazaar.
- Добавляем просмотр отдельного дня. То есть это выборка из базы по конкретному дню, ссылки на редактирование отдельных записей (покупок), собственно ценники, и сумма за день. Как - добавляем новый метод в контроллер, добавляем отдельную вьюху для html-я, добавляем правило для урлов. all right, let's get started
- Чтобы было понятно - начинаем с правила для урлов. хотим чтобы запрос для странички дня выглядел типа blah-blah-blah/view/day/2010/04/30 ну типа никаких идиотских GET параметров. В urls.py добавляем следующее правило:
(r'^purchases/view/(?P<year>\d{4})/(?P<month>\d{1,2})/(?P<day>\d{1,2})/$', 'day_view')
- Новый метод контроллера (в файле purchases/views.py), логика такая - добавленное в предыдущем пункте правило добавит необходимые параметры день/месяц/год, мы из них делаем нормальный объект datetime (если не получается - типа 68 января - то 404), потом забираем из базы все записи Purchase с этой датой, берем сумму по ценам и выводим все во вьюху.
def day_view(request, year, month, day):
try:
dt = datetime.date(year=int(year), month=int(month), day=int(day))
except:
raise Http404
plist = Purchase.objects.filter(purchase_date = dt)
price_sum = Decimal('0.0')
for it in plist:
price_sum += it.price_total
return render_to_response('purchases/day_view.html', {
'date': dt,
'list': plist,
'price_sum': price_sum,
})
- Создаем новый темплейт вьюхи templates/purchases/day_view.html с примерно следующим содержимым. Логика такая - пробегаем по списку значений как на главной странице, выводим ссылки на изменение и тд, выводим отдельно заранее посчитанный тотал.
<p>Date: {{date}} </p>
{% if list %}
<ul>
{% for p in list %}
<li>
{{p.name}}
<b>{{p.price_total}}</b>
<a href='/purchases/edit/{{p.pk}}/'>Edit</a>
</li>
{% endfor %}
</ul>
{% else %}
<p>No purchases yet</p>
{% endif %}
<p>
Total: <b>{{price_sum}}</b>
</p>
<a href='/purchases/add/'>Add</a>
- Проверяем на работоспособность. Должно работать :)
- Теперь делаем тоже самое но для месяца. Тупо по аналогии.
- Добавляем аналогично просмотру дня правило для просмотра месяца (доступ по ссылке типа blah-blah-blah/view/day/2010/04/):
(r'^purchases/view/(?P<year>\d{4})/(?P<month>\d{1,2})/$', 'month_view')
- В методе контроллера имеем сразу же несколько проблем. Первая - нужно сделать запрос на совпадение и года, и месяца одновременно (в случае с просмотром дня - все было просто - тупо совпадение даты). Лечим следующим методом:
from django.db.models import Q
plist = Purchase.objects.filter(Q(purchase_date__year=dt.year) & Q(purchase_date__month=dt.month))
- Вторая проблема - нет в стандартной библиотеке пайтона функций типа добавить месяц. Можно есть - добавления отрезка времени, но так как в месяцах непостоянное кол-во дней такой способ выглядит очень корявым. Какой по-проще способ нашел - по дате составить новую с месяцем+1.
# dt - дата
y, m, d = dt.timetuple()[:3]
dt_next = datetime.date(year=y+((m+1)/12), month=(m+1)%12, day=d)
- Третья проблема - как передать данные во вьюху. Я выбрал способ через словарь (dictionary) - в котором date => purchase_item.
- Вот получившийся код. Логика такая - получаем год/месяц - строим дату как первое число месяца. Выбираем из базы все записи purchase за этот месяц. Потом прибавляя по дню пока не прийдем к первому числу следующего месяца, попутно собирая суммы за каждый день и сумму за весь месяц.
def month_view(request, year, month):
try:
dt = datetime.date(year=int(year), month=int(month), day=1)
except:
raise Http404
from django.db.models import Q
plist = Purchase.objects.filter(Q(purchase_date__year=dt.year) & Q(purchase_date__month=dt.month)).order_by('purchase_date')
y, m, d = dt.timetuple()[:3]
dt_next = datetime.date(year=y+((m+1)/12), month=(m+1)%12, day=d)
day_price = {}
month_total = Decimal('0.0')
while (dt < dt_next):
purchases_day = plist.filter(purchase_date__day=dt.day)
day_total = Decimal('0.0')
for it in purchases_day:
day_total += it.price_total
month_total += day_total
day_price[dt] = day_total
dt += datetime.timedelta(days=1)
return render_to_response('purchases/month_view.html', {
'date': datetime.date(year=int(year),month=int(month),day=1),
'dict': day_price,
'total': month_total,
})
- Создаем новый темплейт templates/purchases/month_view.html
<p>Date: {{date}} </p>
{% if dict %}
<ul>
{% for key,value in dict.items %}
<li>
{{key}}
<b>{{value}}</b>
<a href='/purchases/view/{{key.year}}/{{key.month}}/{{key.day}}/'>Edit</a>
</li>
{% endfor %}
</ul>
{% else %}
<p>No purchases yet</p>
{% endif %}
<p>
Total: <b>{{total}}</b>
</p>
<a href='/purchases/add/'>Add</a>
- Проверяем. Опа, не работает - все дни вперемешку, хотя и выставлено для запроса order_by. А тут начинаются темные, не хорошо документированные особенности джанги по сортировке словаря (я об этот момент чуть голову не сломал). Есть миллион решений - типа сделать буферный список из отсортированных ключей и его передавать с моделью, и тп. Но вот чтобы применить к for-у для словаря сортировку прямо в темплейте - нашел только один:
for key,value in dict.items|dictsort:"0"
- Сделаем так чтобы после Edit-а или Add-а происходил редирект на просмотр использованного дня.
- Метод Add-а:
def purchase_add(request):
if request.method == 'POST':
form = PurchaseItemForm(request.POST)
if form.is_valid():
form.save()
purchase_date = form.cleaned_data["purchase_date"]
return HttpResponseRedirect('/purchases/view/%s/%s/%s/' % purchase_date.timetuple()[:3])
else:
form = PurchaseItemForm()
return render_to_response('purchases/purchase_add.html', {
'form': form
})
- Метод edit-а:
def purchase_edit(request, purchase_id):
try:
item = Purchase.objects.get(pk=purchase_id)
except Purchase.DoesNotExist:
raise Http404
if request.method == 'POST':
form = PurchaseItemForm(request.POST, instance=item)
form.save()
purchase_date = form.cleaned_data["purchase_date"]
return HttpResponseRedirect('/purchases/view/%s/%s/%s/' % purchase_date.timetuple()[:3])
form = PurchaseItemForm(instance=item)
return render_to_response('purchases/purchase_edit.html', {
'form': form,
'id' : purchase_id
})
- Делаем добавление покупки на конкретный день (а не только на сегодня) - те если хотим добавить покупку за вчера со страницы вчерашнего дня - то поле дата должно быть корректно подставлено на вчера. Другими словами blah-blah-blah/purchases/add/ - должно добавлять покупку на сегодня, а blah-blah-blah/purchases/add/2010/04/30 - должно подставлять 2010.04.30
-
В urls.py добавляем следующий словарь для определения сегодня:
import datetime
year, month, day = datetime.datetime.today().timetuple()[:3]
today_dict = {'year':year, 'month':month, 'day':day}
И следующее правила взамен старого add-а:
(r'^purchases/add/$', 'purchase_add', today_dict),
(r'^purchases/add/(?P<year>\d{4})/(?P<month>\d{1,2})/(?P<day>\d{1,2})/$', 'purchase_add'),
- Метод add должен теперь выглядеть так:
def purchase_add(request, year, month, day):
if request.method == 'POST':
form = PurchaseItemForm(request.POST)
if form.is_valid():
form.save()
purchase_date = form.cleaned_data["purchase_date"]
return HttpResponseRedirect('/purchases/view/%s/%s/%s/' % purchase_date.timetuple()[:3])
else:
try:
dt = datetime.date(year=int(year), month=int(month), day=int(day))
except:
dt = datetime.today()
purchaseitem = Purchase()
purchaseitem.purchase_date = dt
form = PurchaseItemForm(instance=purchaseitem)
return render_to_response('purchases/purchase_add.html', {
'form': form
})
- Ну и собственно линка внутри templates/purchases/day_view.html
<a href='/purchases/add/{{date.year}}/{{date.month}}/{{date.day}}/'>Add</a>
Посты по теме: