RkBlog

Hardware, programming and astronomy tutorials and reviews.

Blog w Django III - Kategorie

W tym artykule zajmiemy się rozbudową samego modułu wiadomości. Dodamy kategorie oraz kilka dodatkowych pól. Jeżeli chodzi o kategorie to możemy zrobić prosty system - dana wiadomość w jednej kategorii (Many-To-One) lub skorzystać z relacji Many-To-Many by móc przypisać jedną wiadomość wielu kategoriom. By nie było za łatwo skorzystamy z tej drugiej możliwości. Oto jak wygląda model aplikacji "news" po zmianach:
# -*- coding: utf-8 -*-
from django.db import models

class NewsCategories(models.Model):
	name = models.CharField(max_length=255, verbose_name='Nazwa Kategorii')
	slug = models.SlugField(max_length=255, unique=True, prepopulate_from=("name", ), verbose_name='Odnośnik')
	icon =  models.ImageField(upload_to='icons', verbose_name='Ikonka Kategorii')
	class Meta:
		verbose_name = "Kategoria"
		verbose_name_plural = "Kategorie"
	class Admin:
		list_display = ('name',)
	def __str__(self):
		return self.name

class News(models.Model):
	category = models.ManyToManyField(NewsCategories, verbose_name='Kategorie')
	title = models.CharField(max_length=255, verbose_name='Tytuł')
	slug = models.SlugField(max_length=255, unique=True, prepopulate_from=("title", ), verbose_name='Odnośnik')
	text = models.TextField(verbose_name='Treść')
	date = models.DateTimeField(blank=True, verbose_name='Data dodania')
	wykop = models.CharField(max_length=255, verbose_name='Wykop', blank=True)
	class Meta:
		verbose_name = "Wiadomość"
		verbose_name_plural = "Wiadomości"
	class Admin:
		list_display = ('title', 'date')
		list_filter = ['date']
		search_fields = ['title', 'text']
		date_hierarchy = 'date'
	def __str__(self):
		return self.title

class NewsComments(models.Model):
	news = models.ForeignKey(News)
	text = models.TextField(verbose_name='Treść')
	author = models.CharField(max_length=255, verbose_name='Autor', blank=True)
	class Meta:
		verbose_name = "Komentarze"
		verbose_name_plural = "Komentarze"
	class Admin:
		list_display = ('text', 'author')
	def __str__(self):
		return self.text
Zmianie uległy istniejące modele tak więc musimy usunąć istniejące tabele w bazie danych i stworzyć je ponownie (syncdb). W przypadku SQLite wystarczy usunąć plik z bazą danych. W powyższym kodzie pojawił się model kategorii wiadomości NewsCategories zawierający trzy pola - nazwa, slug i ikona. Pole SlugField będzie zawierało bezpieczną dla odnośników wersję nazwy kategorii (wykorzystamy ją do tworzenia ładnych odnośników). Pole na ikonkę to ImageField, które będzie zapisywało przesłane grafiki w site_media/icons. W modelu News pojawiły się trzy pola - pole zależności ManyToManyField do kategorii, pole SlugField na odnośnik oraz opcjonalne pole tekstowe "wykop" przeznaczone na link do strony na wykop.pl z linkiem do newsa na naszej stronie (o co chodzi zobaczysz później).
Po utworzeniu tabel i uruchomieniu serwera deweloperskiego, w Panelu Admina powinniśmy zobaczyć coś takiego:
djxblog4
Możemy dodać kilka kategorii. Wpisując nazwę pojawi się od razu tekst w polu odnośnika (automat nie obsługuje w pełni poprawnie polskich znaków - nie daje żadnej litery):
djxblog5
Dodając wiadomość możemy wybrać pasujące kategorie:
djxblog6


Dodaj kilka wiadomości, kilka z nich przypisz do więcej niż jednej kategorii. Gdy już mamy przykładowe dane możemy zabrać się za aktualizację skryptu. Zaczniemy od odnośnika do szczegółowego widoku wiadomości. Obecnie jest to /news/ID/. W urls.py zmieniamy wyrażenie regularne:
(r'^news/(?P<slug>[\w\-_]+)/$', 'news.views.show_news'),
Odpowiednio zmieniamy kod widoku:
def show_news(request, slug):
	news = News.objects.get(slug=slug)
	# tworzymy manipulator
	manipulator = NewsComments.AddManipulator()
	if request.POST:
		# formularz wysłany
		data = request.POST.copy()
		# dodajemy brakujące dane
		data['author'] = str(request.user)
		data['news'] = news.id
		errors = manipulator.get_validation_errors(data)
		# jeżeli nie ma błędów zapisz dane i odświerz stronę - przekieruj na ten sam url
		if not errors:
			new = manipulator.save(data)
			return HttpResponseRedirect('/news/'+ str(slug) +'/')
	else:
		# formularz nie wysłany
		errors = {}
		data = {}
	# formularz
	form = forms.FormWrapper(manipulator, data, errors)
	comments = NewsComments.objects.filter(news=news.id)
	return render_to_response('show.html', {'news':news, 'form':form, 'comments':comments})
Zamiast po id wiadomośc identyfikujemy po odnośniku: News.objects.get(slug=slug). Następnie w list.html zmieniamy odnośnik do widoku z /news/{{ new.id }}/ na /news/{{ new.slug }}/ i gotowe.
djxblog7
Teraz wyświetlmy listę kategorii przy wiadomości na widoku szczegółowym. Do szablonu show.html pod treścią wiadomości dodajemy:
<h3>{% for c in news.category.all %}
<a href="/news/cat/{{ c.slug }}/">{{ c }}</a> 
{% endfor %}</h3>
news.category to pole określające powiązanie. By uzyskać listę wszystkich powiązanych obiektów (kategorii) trzeba dodać ".all". Podobnie możemy zrobić dla listy wiadomości. W powyższym kodzie zastosowaliśmy odnośnik, który jeszcze nie istnieje: /news/cat/{{ c.slug }}/ - taki odnośnik wykorzystamy do listowania wiadomości z danej kategorii. Musimy stworzyć widok i dodać odpowiednią regułę do urls.py. Oto widok:
def by_category(request, slug):
	from django.views.generic.list_detail import object_list
	cat = NewsCategories.objects.get(slug=slug)
	news = cat.set.all().order_by('-id')
	return object_list(request, news, paginate_by = 5, allow_empty = True, template_name = 'category_list.html', extra_context={'cat': slug})
A reguła w urls.py:
(r'^news/cat/(?P<slug>[\w\-_]+)/?$','news.views.by_category'),
A szablon:
{% extends "index.html" %}
{% block content %}
	{% if object_list %}
		{% for new in object_list %}
			<h3>{{ new.title }}</h3>
			<p>{{ new.text }}<br />{{ new.date|truncatewords:"1" }} | <b><a href="/news/{{ new.slug }}/">Komentarze</a></b>: {{ new.newscomments_set.count }} | <b>Kategorie</b>: {% for c in new.category.all %}<a href="/news/cat/{{ c.slug }}/">{{ c }}</a> {% endfor %}</p>
		{% endfor %}
		
		{% if has_previous %}
			<div style="text-align:center;"><a href="/news/cat/{{ cat }}/?page={{ previous }}"><b>Nowsze Wiadomości</b></a></div>
		{% endif %}
		{% if has_next %}
			<div style="text-align:center;"><a href="/news/cat/{{ cat }}/?page={{ next }}"><b>Starsze Wiadomości</b></a></div>
		{% endif %}
	{% else %}
		Brak wiadomości
	{% endif %}
{% endblock %}
Zastosowaliśmy widok rozszerzający generyczny widok listowania ze stronicowaniem (generic.list_detail) oraz szablon podobny do listy z głównej strony.

Teraz zajmiemy się wyświetleniem listy kategorii po prawej stronie strony. Wystarczy pobrać listę kategorii. Jednakże by była widoczna na każdej podstronie to każdy widok musiałby przekazywać zmienną z taką listą. By uniknąć takiego problemu zastosujemy własny TEMPLATE_CONTEXT_PROCESSORS, który będzie przekazywał do szablonów określone zmienne, niezależnie od tego co przekazują widoki. W katalogu projektu ("blog") stwórz plik o nazwie globals.py, o kodzie:
from django.conf import settings
from blog.news.models import *

def blog(request):
	return {'categories': NewsCategories.objects.all()}
Do settings.py dodaj:
TEMPLATE_CONTEXT_PROCESSORS = ("django.core.context_processors.auth",
"django.core.context_processors.debug",
"django.core.context_processors.i18n",
"globals.blog",)
Pierwsze trzy wpisy to domyślnie włączone preprocesory django, które musimy podać jeżeli określamy własną listę preprocesorów. Ostatni wpis jest nasz, a składnia wpisu ma postać plik.funkcja. Jak widzimy funkcja blog zwraca słownik z kluczem "categories". W szablonie index.html wycinamy prawe menu i dajemy:
<h3>Kategorie</h3>
				<ul>
					{% for c in categories %}
					<li><img src="/site_media/{{ c.icon }}" alt="" /> <a href="/news/cat/{{ c.slug }}/">{{ c.name }}</a></li>
					{% endfor %}
				</ul>
Zamiast menu zobaczymy listę kategorii. Lista ta będzie widoczna na wszystkich generycznych widokach. U nas tylko show_news nie jest generycznym widokiem. Wymaga on drobnej modyfikacji. W views.py dodaj:
from django.template import RequestContext
A do "render_to_response" dodaj , context_instance=RequestContext(request):
return render_to_response('show.html', {'news':news, 'form':form, 'comments':comments}, context_instance=RequestContext(request))
Gotowe:
djxblog8
W kolejnym artykule zajmiemy się polem "wykop" oraz stworzymy kanały RSS oraz mapę strony Sitemap.

Źródła

Pobierz źródła
Po rozpakowaniu edytuj ścieżkę w urls.py a następnie stwórz tabele i superadmina.
RkBlog

Django, 14 July 2008, Piotr Maliński

Comment article