RaumZeitLabor Mannheim, 13.12.2014
Wir bauen eine Blogsoftware.
(Was besseres ist mir auch nicht eingefallen.)
def square(a):
return a*a
print(square(3)) # 9
class Foo(Bar):
def __init__(self, a):
self.var = a
<!DOCTYPE html>
<html>
<head>
<title>Hallo</title>
</head>
<body>
<h1>Hallo</h1>
<p>Dies ist eine Website!</p>
</body>
</html>
^([a-zA-Z0-9.-_]+)@([a-zA-Z0-9.-]+)\.([a-zA-Z]{2,})$
raphael $ python --version
Python 3.4.2
raphael $ pip --version
pip 1.5.6 from /usr/lib/python3.4/site-packages (python 3.4)
Wir wollen Python 3!
Python <3.4
raphael $ sudo pip install virtualenv
raphael $ virtualenv env
Python 3.4+
raphael $ pyvenv env
raphael $ source env/bin/activate
(env) raphael $
(env) raphael $ pip install Django==1.7.1
(env) raphael $ django-admin.py startproject workshopproject
(env) raphael $ cd workshopproject/
(env) raphael workshopproject/ $
(env) raphael workshopproject/ master $ tree .
.
├── manage.py
└── workshopproject
├── __init__.py
├── settings.py
├── urls.py
└── wsgi.py
1 directory, 5 files
INSTALLED_APPS = (
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
)
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'Europe/Berlin'
USE_I18N = True
USE_L10N = True
USE_TZ = True
(env) raphael workshopproject/ $ python manage.py migrate
Operations to perform:
Apply all migrations: auth, admin, contenttypes, sessions
Running migrations:
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying sessions.0001_initial... OK
(env) raphael workshopproject/ $ python manage.py runserver
Performing system checks...
System check identified no issues (0 silenced).
November 08, 2014 - 09:38:18
Django version 1.7.1, using settings 'workshopproject.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
(env) raphael workshopproject/ $ python manage.py startapp blog
(env) raphael workshopproject/ $ tree blog
blog
├── admin.py
├── __init__.py
├── migrations
│ └── __init__.py
├── models.py
├── tests.py
└── views.py
1 directory, 6 files
INSTALLED_APPS = (
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'blog',
)
blog/models.py
from django.db import models
class Blogpost(models.Model):
title = models.CharField(max_length=255)
text = models.TextField()
pub_date = models.DateTimeField()
class Comment(models.Model):
post = models.ForeignKey(Blogpost, related_name="comments")
name = models.CharField(max_length=255)
text = models.TextField()
pub_date = models.DateTimeField()
(env) raphael workshopproject/ $ python manage.py makemigrations blog
Migrations for 'blog':
0001_initial.py:
- Create model Blogpost
- Create model Comment
(env) raphael workshopproject/ $ python manage.py sqlmigrate blog 0001BEGIN; CREATE TABLE "blog_blogpost" ( "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "title" varchar(255) NOT NULL, "text" text NOT NULL, "pub_date" datetime NOT NULL); CREATE TABLE "blog_comment" ( "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "name" varchar(255) NOT NULL, "text" text NOT NULL, "pub_date" datetime NOT NULL, "post_id" integer NOT NULL REFERENCES "blog_blogpost" ("id")); CREATE INDEX blog_comment_f3aa1999 ON "blog_comment" ("post_id"); COMMIT;
(env) raphael workshopproject/ $ python manage.py migrate
Operations to perform:
Apply all migrations: auth, admin, sessions, blog, contenttypes
Running migrations:
Applying blog.0001_initial... OK
(env) raphael workshopproject/ $ python manage.py shell
Python 3.4.2 (default, Oct 8 2014, 13:44:52)
[GCC 4.9.1 20140903 (prerelease)] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>>
>>> from blog.models import Blogpost, Comment
>>> Blogpost.objects.all()
[]
>>> post = Blogpost(title="First blogpost", text="Hello World")
>>> from django.utils import timezone
>>> post.pub_date = timezone.now()
>>> post.save()
>>> post.id
1
>>> post.text = "Hallo Welt!"
>>> post.save()
>>> Blogpost.objects.all()
[<Blogpost: Blogpost object>]
Well…
blog/models.py
class Blogpost(models.Model):
…
def __str__(self):
return self.title
$ ./manage.py shell
>>> from blog.models import Blogpost, Comment
>>> Blogpost.objects.all()
[<Blogpost: First blogpost>]
>>> Blogpost.objects.filter(id=1)
[<Blogpost: First blogpost>]
>>> Blogpost.objects.filter(title__startswith="First")
[<Blogpost: First blogpost>]
>>> Blogpost.objects.filter(pub_date__year=2014)
[<Blogpost: First blogpost>]
>>> post = Blogpost.objects.get(id=1)
<Blogpost: First blogpost>
from django.utils import timezone
import datetime
class Blogpost(models.Model):
…
def was_published_recently(self):
return self.pub_date >= timezone.now() - datetime.timedelta(days=1)
>>> post.was_published_recently()
True
>>> post = Blogpost.objects.get(id=1)
>>> post
<Blogpost: First blogpost>
>>> post.comments.count()
0
>>> from django.utils import timezone
>>> post.comments.create(name="Alice", text="Hallo!",
pub_date=timezone.now())
<Comment: Comment object>
>>> post.comments.all()
[<Comment: Comment object>]
>>> Comment.objects.filter(post__pub_date__year=2014)
[<Comment: Comment object>]
0
>>> c = Comment.objects.get(id=1)
>>> c.delete()
>>> post.comments.count()
filter, excludeorder_by, distinct, [0:30]values, …none, allselect_related, prefetch_relatedget, first, last, …aggregateexists, countcreate, update, delete
(env) raphael workshopproject/ $ python manage.py createsuperuser
Username (leave blank to use 'raphael'): admin
Email address: admin@example.com
Password:
Password (again):
Superuser created successfully.
(env) raphael workshopproject/ $ python manage.py runserver
Performing system checks...
System check identified no issues (0 silenced).
November 08, 2014 - 09:38:18
Django version 1.7.1, using settings 'workshopproject.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
Aber… wo sind die Blogposts?
blog/admin.py
from django.contrib import admin
from blog.models import Blogpost
admin.site.register(Blogpost)
Aber… wo sind die Kommentare?
Der Adminbereich kann ganz viel tolles Zeug.
Aber das ist heute nicht das Thema.
Lest das offizielle Tutorial.
blog/views.py
from django.http import HttpResponse
def index(request):
return HttpResponse("Hello, world. This is my blog.")
blog/urls.py
from django.conf.urls import patterns, url
from blog import views
urlpatterns = patterns('',
url(r'^$', views.index, name='index'),
)
workshopproject/urls.py
from django.conf.urls import patterns, url
from django.contrib import admin
urlpatterns = patterns('',
url(r'^blog/', include('blog.urls')),
url(r'^admin/', include(admin.site.urls)),
)
http://localhost:8000/blog/
blog/templates/blog/base.html
<!DOCTYPE html>
<html>
<head>
<title>Unser Blog</title>
</head>
<body>
{% block content %}
{% endblock %}
</body>
</html>
blog/templates/blog/index.html
{% extends "blog/base.html" %}
{% block content %}
Hallo!
{% endblock %}
blog/views.py
from django.shortcuts import render
def index(request):
return render(request, "blog/index.html")
blog/views.py
from django.shortcuts import render
from blog.models import Blogpost
def index(request):
context = {
'posts': Blogpost.objects.all().order_by('-pub_date')
}
return render(request, "blog/index.html", context)
blog/templates/blog/index.html
{% extends "blog/base.html" %}
{% block content %}
{% for post in posts %}
<article>
<h2>{{ post.title }}</h2>
<p>{{ post.pub_date|date:"d.m.Y" }}</p>
{{ post.text }}
</article>
{% endfor %}
{% endblock %}
blog/urls.py
urlpatterns = patterns('',
url(r'^post/(?P<postid>[0-9]+)$', views.detail, name='detail'),
url(r'^$', views.index, name='index'),
)
blog/templates/blog/detail.html
{% extends "blog/base.html" %}
{% block content %}
<article>
<h2>{{ post.title }}</h2>
<p>{{ post.pub_date|date:"d.m.Y" }}</p>
{{ post.text }}
</article>
{% for comment in comments %}
<article>
<p>Kommentar von {{ comment.name }}</p>
<p>{{ comment.text }}</p>
</article>
{% endfor %}
{% endblock %}
blog/views.py
from django.shortcuts import render, get_object_or_404
from blog.models import Blogpost, Comment
def detail(request, postid):
post = get_object_or_404(Blogpost, id=postid)
context = {
'post': post,
'comments': post.comments.all()
}
return render(request, "blog/detail.html", context)
blog/templates/blog/index.html
{% extends "blog/base.html" %}
{% block content %}
{% for post in posts %}
<article>
<h2><a href="{% url "detail" postid=post.id %}">
{{ post.title }}
</a></h2>
<p>{{ post.pub_date|date:"d.m.Y" }}</p>
{{ post.text }}
</article>
{% endfor %}
{% endblock %}
blog/views.py
from django.utils.timezone import now
from django import forms
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ('name', 'text',)
blog/views.py
def detail(request, postid):
post = get_object_or_404(Blogpost, id=postid)
form = CommentForm()
context = {
'post': post,
'comments': post.comments.all(),
'form': form
}
return render(request, "blog/detail.html", context)
blog/templates/blog/detail.html
<form method="post" action="">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Kommentar absenden</button>
</form>
def detail(request, postid):
post = get_object_or_404(Blogpost, id=postid)
if request.method == "POST":
new_comment = Comment(post=post, pub_date=now())
form = CommentForm(data=request.POST, instance=new_comment)
if form.is_valid():
form.save()
# Empty form input
form = CommentForm()
else:
form = CommentForm()
context = {
'post': post,
'comments': post.comments.all(),
'form': form
}
return render(request, "blog/detail.html", context)
blog/views.py
from django.views import generic
class IndexView(generic.ListView):
template_name = 'blog/index.html'
context_object_name = 'posts'
def get_queryset(self):
return Blogpost.objects.order_by('-pub_date')
blog/urls.py
url(r'^$', views.IndexView.as_view(), name='index'),
blog/views.py
from django.core.urlresolvers import reverse
class DetailView(generic.CreateView):
template_name = 'blog/detail.html'
model = Comment
fields = ('name', 'text')
@property
def blogpost(self):
return get_object_or_404(
Blogpost, id=self.kwargs['postid'])
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['post'] = self.blogpost
context['comments'] = context['post'].comments.all()
return context
def form_valid(self, form):
form.instance.post = self.blogpost
form.instance.pub_date = now()
return super().form_valid(form)
def get_success_url(self):
return reverse('detail', kwargs={
'postid': self.blogpost.id
})
blog/urls.py
url(r'^post/(?P<postid>[0-9]+)$',
views.DetailView.as_view(), name='detail'),
settings.py
STATIC_URL = '/static/'
blog/static/blog/style.css
body {
font-family: sans-serif;
}
blog/templates/blog/base.html
{% load static %}
<!DOCTYPE html>
<html>
<head>
<title>Unser Blog</title>
<link rel="stylesheet" type="text/css"
href="{% static "blog/style.css" %}">
</head>
<body>
{% block content %}
{% endblock %}
</body>
</html>
Weh Es … wat?
pip install gunicorn
gunicorn myproject.wsgi
→ Dokumentation
WSGIScriptAlias / /path/to/example.com/mysite/wsgi.py
WSGIPythonPath /path/to/example.com
Alias /media/ /path/to/example.com/media/
Alias /static/ /path/to/example.com/static/
<Directory /path/to/example.com/mysite>
<Files wsgi.py>
Require all granted
</Files>
</Directory>
→ Dokumentation
DEBUG = False
ALLOWED_HOSTS = ['example.com']
ADMINS = (
('Raphael Michel', 'raphael@abiapp.net'),
)
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'filters': {
'require_debug_false': {
'()': 'django.utils.log.RequireDebugFalse'
}
},
'handlers': {
'mail_admins': {
'level': 'ERROR',
'filters': ['require_debug_false'],
'include_html': True,
'class': 'django.utils.log.AdminEmailHandler'
}
},
'loggers': {
'django.request': {
'handlers': ['mail_admins'],
'level': 'ERROR',
'propagate': True,
},
}
}