RkBlog

Hardware, programming and astronomy tutorials and reviews.

Formularze w Django (Oldforms)

Niniejszy artyku艂 opisuje system formularzy i manipulator贸w "starego" systemu. Dla Django >= 0.96 domy艣lnym systemem obs艂ugi formularzy b臋dzie nowy system zwany obecnie newforms.

Manipulatory Add i Change

Zak艂adamy 偶e mamy taki model:
from django.db import models

PLACE_TYPES = (
    (1, 'Bar'),
    (2, 'Restauracja'),
    (3, 'Kino'),
    (4, 'Teatr'),
)

class Place(models.Model):
    name = models.CharField(maxlength=100)
    address = models.CharField(maxlength=100, blank=True)
    city = models.CharField(maxlength=50, blank=True)
    state = models.USStateField()
    zip_code = models.CharField(maxlength=5, blank=True)
    place_type = models.IntegerField(choices=PLACE_TYPES)
    class Admin:
        pass
    def __str__(self):
        return self.name
Generuje on nam 艣liczny formularz w Panelu Admina ale my chcemy da膰 u偶ytkownikom mo偶liwo艣膰 dodawania miejsc (Place).

Manipulatory

Manipulatory to najwy偶szego poziomu interfejs do dodawania i edycji obiekt贸w. W django mamy dwa interesuj膮ce nas manipulatory: AddManipulator i ChangeManipulator.
Oto przyk艂ad pobieraj膮cy dane wys艂ane POSTem i zapisuj膮cy dane jako nowy obiekt:
from django.shortcuts import render_to_response
from django.http import Http404, HttpResponse, HttpResponseRedirect
from django import forms
# importujemy nasz model
from mysite.myapp.models import Place

def naive_create_place(request):
    # Tworzymy manipulator
    manipulator = Place.AddManipulator()

    # pobieramy dane z POST, tworzymy ich kopi臋
    new_data = request.POST.copy()

    # konwersja danych (wszystko to 艂a艅cuchy)
    # na odpowiednie typy pythona dla danych p贸l.
    manipulator.do_html2python(new_data)

    # zapisujemy
    new_place = manipulator.save(new_data)

    # zrobione
    return HttpResponse("Miejsce stworzone: %s" % new_place)
Powy偶szy przyk艂ad pokazuje dzia艂anie manipulator贸w lecz nie jest kompletnym rozwi膮zaniem:
Mo偶na stworzy膰 jeden widok obs艂uguj膮cy i formularz i zapis danych wraz z walidacj膮:
from django.shortcuts import render_to_response
from mojprojekt.model.models import *
from django.http import Http404, HttpResponse, HttpResponseRedirect
from django import forms

# widok formularza
def create_place(request):
    manipulator = Place.AddManipulator()

    if request.POST:
        # dane wys艂ane POSTem, kopiujemy je i chcemy tworzy膰 nowy obiekt Place
        new_data = request.POST.copy()

        # Sprawdzamy b艂臋dy
        # je偶eli b臋d膮 formularz prze艂aduje si臋 zachowuj膮c dane
        # bo s膮 one w new_data
        errors = manipulator.get_validation_errors(new_data)

        if not errors:
            # Brak b艂臋d贸w czyli zapisujemy!
            manipulator.do_html2python(new_data)
            new_place = manipulator.save(new_data)

           # przekierowanie na widok wpis贸w
           # zawsze po wys艂aniu formularza gdzie艣
           # przekierowuj by unikn膮膰 dodawania klon贸w danych
            return HttpResponseRedirect("/view/")
    else:
        # Brak danych POST, chcemy czysty formularz
        errors = new_data = {}

    # Tworzymy wrappera, szablon i reszt臋
    form = forms.FormWrapper(manipulator, new_data, errors)
    return render_to_response('create_form.html', {'form': form})

# widok wszystkich wpis贸w
def view_place(request):
	all_entries = Place.objects.all()
	return render_to_response('view.html', {'tup': all_entries})
Szablon create_form.html:
<h1>Dodaj Miejsce:</h1>

{% if form.has_errors %}
Popraw b艂臋dy: {{ form.error_dict|pluralize }}:
{% endif %}

<form method="post" action=".">
<p>
    Imi臋: {{ form.name }}
    {% if form.name.errors %}*** {{ form.name.errors|join:", " }}{% endif %}
</p>
<p>
    Adres: {{ form.address }}
    {% if form.address.errors %}*** {{ form.address.errors|join:", " }}{% endif %}
</p>
<p>
    Miasto: {{ form.city }}
    {% if form.city.errors %}*** {{ form.city.errors|join:", " }}{% endif %}
</p>
<p>
    Stan USA: {{ form.state }}
    {% if form.state.errors %}*** {{ form.state.errors|join:", " }}{% endif %}
</p>
<p>
    Kod pocztowy: {{ form.zip_code }}
    {% if form.zip_code.errors %}*** {{ form.zip_code.errors|join:", " }}{% endif %}
</p>
<p>
    Typ miejsca: {{ form.place_type }}
    {% if form.place_type.errors %}*** {{ form.place_type.errors|join:", " }}{% endif %}
</p>
<input type="submit" />
</form>


Szablon view.html:
{% for o in tup %}
	<li>{{o.name}} - {{o.city}}</li>
{% endfor %}

urls.py:
(r'^form/$', 'projekt.aplikacja.views.create_place'),
(r'^view/$', 'projekt.aplikacja.views.view_place'),
Pod /form/ mamy formularz a pod /view/ list臋 wszystkich wpis贸w.

Edycja wygl膮da podobnie, oto widok:
def edit_place(request, place_id):
   # je偶eli rekord o podanym id istnieje to stw贸rz ChangeManipulator
    try:
        manipulator = Place.ChangeManipulator(place_id)
    except Place.DoesNotExist:
        raise Http404

    # Pobieramy oryginalne dane
    place = manipulator.original_object

    if request.POST:
        new_data = request.POST.copy()
        errors = manipulator.get_validation_errors(new_data)
        if not errors:
            manipulator.do_html2python(new_data)
            manipulator.save(new_data)

            # przekierowanie
            return HttpResponseRedirect("/view/")
    else:
        errors = {}
        # Dzi臋ki temu formularz poprawnie rozpozna dane dla ka偶dego pola
        new_data = place.__dict__

    form = forms.FormWrapper(manipulator, new_data, errors)
    return render_to_response('edit_form.html', {'form': form, 'place': place})
ChangeManipulator potrzebuje numeru id, kt贸ry ma by膰 edytowany. Szablon jest identyczny jak ten dla dodawania. W urls.py pod艂膮czy膰 mo偶emy to tak:
(r'^edit/(\d+)/$', 'djn.test.views.edit_place'),
I pod /edit/NUMER/ mie膰 formularz edycji wpisu o danym id.

Wysy艂anie plik贸w

Wysy艂anie plik贸w poprzez formularze mo偶e dotyczy膰 p贸l typu FileField i ImageField w danym modelu co obs艂ugiwane jest przez FormWrapper lub niepowi膮zanego z modelem zwyk艂ego formularza. Zaczniemy od tegu drugiego. Stw贸rz szablon o kodzie:
<form enctype="multipart/form-data" method="post" action=".">
<input type="file" name="plik"><br />
<input type="text" name="tytul"><br />
<input type="submit" value="Wy艣lij">
</form>
Jest to prosty formularz z polem typu file oraz, co jest konieczne zawiera atrybut:
enctype="multipart/form-data"
w tagu FORM.
Najprostszy widok obs艂uguj膮cy go wygl膮da艂by tak:
from django.shortcuts import render_to_response

def test(request):
	if request.POST:
		data = request.POST.copy()
		data.update(request.FILES)
		print data
	return render_to_response('test.html')
Po wys艂aniu formularza dane o plikach wys艂anych razem z formularzem dost臋pne s膮 pod request.FILES. W powy偶szym widoku data zawiera dane POST z formularz jak i dane o plikach do艂膮czone poprzez:
data.update(request.FILES)
W konsoli, w kt贸rej dzia艂a serwer deweloperski zobaczymy co艣 takiego:
<MultiValueDict: {'plik': [{'content': 'ZAWARTO艢膯', 'content-type': 'TYP MIME', 'filename': 'NAZWA PLIKU'}], 'tytul': ['WARTO艢膯 POLA']}>
Gdzie plik to nazwa pola typu file z szablonu. Teraz trzeba zawarto艣膰 pliku zapisa膰 na serwerze.
Nie zapominaj o walidacji typ贸w plik贸w 艂adowanych przez u偶ytkownik贸w. Sprawdzaj typ MIME jak i rozszerzenie pliku! Katalog docelowy (przewa偶nie gdzie艣 w /site_media/) powinien znajdowa膰 si臋 poza katalogiem serwera tak by przes艂ane skrypty (Perl, PHP itp.) nie mog艂y by膰 wykorzystane.
Oto zmodyfikowany widok zapisuj膮cy tylko pliki tekstowe pod nazw膮 podan膮 w polu "tytul":
from django.shortcuts import render_to_response
from django.conf import settings

def test(request):
	if request.POST:
		data = request.POST.copy()
		data.update(request.FILES)
		if data.has_key('plik') and data['plik']['content-type'] == 'text/plain' and data['plik']['filename'].split('.')[-1] == 'txt':
			a = open(settings.MEDIA_ROOT + data['tytul'] + '.txt', 'w')
			a.write(data['plik']['content'])
			a.close()
	return render_to_response('test.html')


Zmienna settings.MEDIA_ROOT to MEDIA_ROOT z settings.py i zawiera pe艂n膮 艣cie偶k臋 do katalogu plik贸w statycznych (/site_media) W przypadku p贸l typu file powi膮zanych z modelem, generowany przez FormWrapper stosujemy w szablonie:
{{ form.POLE }}{{ form.POLE_file }}
Gdzie POLE to nazwa pola typu FileField czy ImageField w naszym modelu. Od strony widoku r贸偶nic nie ma. Zalecam r贸wnie偶 zapoznanie si臋 z dokumentacj膮 tych p贸l.

W艂asne Manipulatory

Mo偶na tworzy膰 w艂asne manipulatory, je偶eli chcemy ustawi膰 specjalne regu艂y walidacji na naszym formularzu. Przyk艂ad - wysy艂anie emaili z formularza przez zalogowanych u偶ytkownik贸w:
# manipulator
class PMessage(forms.Manipulator):
	def __init__(self):
		self.fields = (
			forms.TextField(field_name="subject", length=30, maxlength=200, is_required=True),
			forms.LargeTextField(field_name="contents", is_required=True),
			)
# widok
def send_pmessage(request, target_user):
	# czy jestem zalogowany i nie wysy艂am do siebie
	if request.user.is_authenticated() and str(request.user) != str(target_user):
		# u偶ywamy naszego manipulatora
		manipulator = PMessage()
		if request.POST:
			new_data = request.POST.copy()
			errors = manipulator.get_validation_errors(new_data)
			if not errors:
				manipulator.do_html2python(new_data)
				# new_data zawiera dane gotowe do wykorzystania
				# tutaj zamiast do bazy wysy艂ane s膮 emailem
				from django.core.mail import send_mail
				ruser = User.objects.get(username=str(request.user))
				send_mail(new_data['subject'], new_data['contents'], request.user.email, [ruser.email], fail_silently=True)
				return HttpResponseRedirect("/user/")
		else:
			errors = new_data = {}
		form = forms.FormWrapper(manipulator, new_data, errors)
		return render_to_response('userpanel/pmessage.html', {'form': form})
	else:
		return HttpResponseRedirect("/user/")
Z formularzem:
<h2>{% trans "Send a Private Message" %}</h2><form action="." method="post">
<div class="content">
          <table>
            <tr class="rowA">
              <td class="first" style="width:25%;"><b>{% trans "Subject" %}</b></td>
              <td>{{ form.subject }}{% if form.subject.errors %}<br />*** {{ form.subject.errors|join:", " }}{% endif %}</td>
            </tr>
            <tr class="rowB">
              <td><b>{% trans "Text" %}</b></td>
              <td>{{ form.contents }}{% if form.contents.errors %}<br />*** {{ form.contents.errors|join:", " }}{% endif %}</td>
            </tr>
          </table>
</div>
<div class="box"><br /><div style="text-align:center;"><input type="submit" value="{% trans "Send Message" %}" style="actiontable"></div><br /></div>
</form>
Klas臋 manipulatora wraz z widokiem umieszczamy w views.py. Najwa偶niejsza r贸偶nica w kodzie widoku to:
manipulator = PMessage()
Tworzymy instancj臋 w艂asnego manipulatora. Celem naszego manipulatora jest opisanie p贸l, ich nazw, typ贸w oraz regu艂 walidacji:
self.fields = (
			forms.TextField(field_name="subject", length=30, maxlength=200, is_required=True),
			forms.LargeTextField(field_name="contents", is_required=True),
			)
Mamy dwa pola subject i contents odpowiednio typu TextField i LargeTextField. Regu艂y walidacji s膮 proste - is_required oznacza i偶 pola s膮 obowi膮zkowe do wype艂nienia. Opr贸cz tego pole "subject" ma podane rozmiary. Zwr贸膰 uwag臋 偶e w szablonie stosujemy nazwy tych p贸l do budowy formularza: {{ form.subject }} itd. Je偶eli chodzi o walidatory to mamy dost臋p do serii prostych takich jak:
Manipulatory, FormWrapper i Walidatory zostan膮 zast膮pione przez nowy system szablon贸w od Django 0.96. Stary system formularzy zostanie usuni臋ty ca艂kowicie w nast臋pnym wydaniu po 1.0. Tworz膮c nowe projekty w niedalekiej przysz艂o艣ci lepszym rozwi膮zaniem mo偶e okaza膰 si臋 newforms.


Na koniec przyk艂ad w艂asnego walidatora:
class LoginForm(forms.Manipulator):
	def __init__(self):
		self.fields = (forms.TextField(field_name="login", length=30, maxlength=200, is_required=True),
		forms.PasswordField(field_name="password", length=30, maxlength=200, is_required=True),
		forms.TextField(field_name="imgtext", is_required=True, validator_list=[self.hashcheck]),
		forms.TextField(field_name="imghash", is_required=True),)
	def hashcheck(self, field_data, all_data):
		import sha
		if not all_data['imghash'] == sha.new(field_data).hexdigest():
			raise validators.ValidationError("Captcha Error.")
Jest to walidator formularza z captch膮 - tekstem na grafice, kt贸ry trzeba wpisa膰. Dodatkowo w formularzu znajduje si臋 hasz tekstu z grafiki - u偶ytkownik wpisa艂 poprawny tekst (pole imgtext) je偶eli hasz tego tekstu jest identyczny z tym przes艂anym wraz z formularzem (pole imghash). By to sprawdzi膰 potrzebny w艂asny walidator definiowany jako metoda klasy
def hashcheck(self, field_data, all_data):
Gdzie nazwa metody staje si臋 nazw膮 walidatora, field_data - dane pola, all_data - dane wszystkich p贸l. W przypadku niepoprawnej walidacji walidator powinien generowa膰 wyj膮tek:
raise validators.ValidationError("Tekst b艂臋du")
Nasz walidator haszuje tekst i sprawdza czy zgadza si臋 z haszem kontrolnym, je偶eli nie - wyj膮tek. Walidator przypisujemy do pola poprzez:
validator_list=[self.hashcheck]
RkBlog

Django, 14 July 2008, Piotr Mali艅ski

Comment article