Forums on a Shared Host
In this chapter, we will setup initial forum pages.
We can see the actual implementation via BoGo Forums.
Some of the screen shots are:
Forums Home (http://www.bogotobogo.com/dj/forums)
Python Forum (http://www.bogotobogo.com/dj/forums/forum/10/)
Thread view of the Python Forum (http://www.bogotobogo.com/dj/forums/thread/12/)
Reply to a thread of the Python Forum (http://www.bogotobogo.com/dj/forums/post/reply/12/)
Starting a new thread of the Python Forum (http://www.bogotobogo.com/dj/forums/post/new_thread/10/)
User Registration (http://www.bogotobogo.com/dj/accounts/register/)
More things to be done (some of the sources are not completed)
- Email activation - sending email / authentication via email / recovery of a password
- User profile editing including user avatar setting
Our forum consists of two apps:
$ python manage.py startapp forum $ python manage.py startapp registration
forum:
registration:
As we can see from the trees, there are some static files, and they are initially stored in each app's static directory under relevant sub-directories. Then we issue collectstatic command (see Managing (Deploying) Static files (CSS, Images, Javascript) on Shared Host):
$ pwd /home2/bogotob1/djangoproject $ python manage.py collectstatic
Note that we need to set the STATIC_ROOT to the directory from which we like to serve these files. So, when we do collectstatic, it will copy all files from our static folders into the STATIC_ROOT directory.I n my case, it is:
STATIC_ROOT = "/home2/bogotob1/www/dj/static/"
INSTALLED_APPS = ( 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'registration', 'djangoapp', 'taggit', 'blog', 'forum', 'myapp', ) MIDDLEWARE_CLASSES = ( 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ) ROOT_URLCONF = 'djangoproject.urls' ... DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'name', 'USER': 'user', 'PASSWORD': 'password', 'HOST': 'localhost', # Or an IP Address that your DB is hosted on 'PORT': '3306', }
from django.db import models from django.contrib.auth.models import User from django.contrib import admin from string import join from djangoproject.settings import MEDIA_ROOT from django.db.models.signals import post_save class Forum(models.Model): title = models.CharField(max_length=60) def __unicode__(self): return self.title def num_posts(self): posts = sum([t.num_posts() for t in self.thread_set.all()]) return posts def last_post(self): if self.thread_set.count(): last = None for t in self.thread_set.all(): l = t.last_post() if l: if not last: last = l elif l.created > last.created: last = l return last class Thread(models.Model): title = models.CharField(max_length=60) created = models.DateTimeField(auto_now_add=True) creator = models.ForeignKey(User, blank=True, null=True) forum = models.ForeignKey(Forum) def __unicode__(self): return unicode(self.creator) + " - " + self.title def num_posts(self): return self.post_set.count() def num_replies(self): return self.post_set.count() - 1 def last_post(self): if self.post_set.count(): return self.post_set.order_by("created")[0] class Post(models.Model): title = models.CharField(max_length=60) created = models.DateTimeField(auto_now_add=True) creator = models.ForeignKey(User, blank=True, null=True) thread = models.ForeignKey(Thread) body = models.TextField(max_length=10000) def __unicode__(self): return u"%s - %s - %s" % (self.creator, self.thread, self.title) def short(self): return u"%s - %s\n%s" % (self.creator, self.title, self.created.strftime("%b %d, %I:%M %p")) short.allow_tags = True def profile_data(self): p = self.creator.userprofile_set.all()[0] return p.posts, p.avatar class UserProfile(models.Model): avatar = models.ImageField("Profile Pic", upload_to="images/", blank=True, null=True) posts = models.IntegerField(default=0) user = models.ForeignKey(User, unique=True) def __unicode__(self): return unicode(self.user) ### Admin class ProfileAdmin(admin.ModelAdmin): list_display = ["user"] class ForumAdmin(admin.ModelAdmin): pass class ThreadAdmin(admin.ModelAdmin): list_display = ["title", "forum", "creator", "created"] list_filter = ["forum", "creator"] class PostAdmin(admin.ModelAdmin): search_fields = ["title", "creator"] list_display = ["title", "thread", "creator", "created"] def create_user_profile(sender, **kwargs): """When creating a new user, make a profile for him or her.""" u = kwargs["instance"] if not UserProfile.objects.filter(user=u): UserProfile(user=u).save() post_save.connect(create_user_profile, sender=User)
Then, to add tables, we need to run:
$ python manage.py syncdb
from django.shortcuts import render from django.contrib.auth.decorators import login_required from django.contrib.auth.models import User # Create your views here. from django.core.urlresolvers import reverse from djangoproject.settings import MEDIA_ROOT, MEDIA_URL from django.http import HttpResponseRedirect, HttpResponse from django.shortcuts import get_object_or_404, render_to_response from django.core.context_processors import csrf from django.core.paginator import Paginator, InvalidPage, EmptyPage from django.forms import ModelForm from django.core.urlresolvers import reverse from forum.models import * class ProfileForm(ModelForm): class Meta: model = UserProfile exclude = ["posts", "user"] @login_required def profile(request, pk): """Edit user profile.""" profile = UserProfile.objects.get(user=pk) img = None if request.method == "POST": pf = ProfileForm(request.POST, request.FILES, instance=profile) if pf.is_valid(): pf.save() # resize and save image under same filename imfn = pjoin(MEDIA_ROOT, profile.avatar.name) im = PImage.open(imfn) im.thumbnail((160,160), PImage.ANTIALIAS) im.save(imfn, "JPEG") else: pf = ProfileForm(instance=profile) if profile.avatar: img = "/media/" + profile.avatar.name return render_to_response("forum/profile.html", add_csrf(request, pf=pf, img=img)) def save_profile(request, pk): """Edit user profile.""" profile = UserProfile.objects.get(user=pk) img = None if request.method == "POST": pf = ProfileForm(request.POST, request.FILES, instance=profile) if pf.is_valid(): pf.save() # resize and save image under same filename imfn = pjoin(MEDIA_ROOT, profile.avatar.name) im = PImage.open(imfn) im.thumbnail((160,160), PImage.ANTIALIAS) im.save(imfn, "JPEG") else: pf = ProfileForm(instance=profile) if profile.avatar: img = "/media/" + profile.avatar.name return render_to_response("forum/profile.html", add_csrf(request, pf=pf, img=img)) def mk_paginator(request, items, num_items): """Create and return a paginator.""" paginator = Paginator(items, num_items) try: page = int(request.GET.get("page", '1')) except ValueError: page = 1 try: items = paginator.page(page) except (InvalidPage, EmptyPage): items = paginator.page(paginator.num_pages) return items def main(request): """Main listing.""" forums = Forum.objects.all() return render_to_response("forum/list.html", dict(forums=forums, user=request.user)) class ThreadAdmin(admin.ModelAdmin): list_display = ["title", "forum", "creator", "created"] list_filter = ["forum", "creator"] class PostAdmin(admin.ModelAdmin): search_fields = ["title", "creator"] list_display = ["title", "thread", "creator", "created"] @login_required def post(request, ptype, pk): """Display a post form.""" action = reverse("forum.views.%s" % ptype, args=[pk]) #t = Thread.objects.get(pk=pk) #f = Forum.objects.get(pk=t.forum.pk) if ptype == "new_thread": f = Forum.objects.get(pk=pk) title = f.title + " Forum - Start New Topic" subject = '' elif ptype == "reply": t = Thread.objects.get(pk=pk) f = Forum.objects.get(pk=t.forum.pk) title = f.title + " Forum - Reply" subject = "Re: " + Thread.objects.get(pk=pk).title return render_to_response("forum/post.html", add_csrf(request, subject=subject, action=action, title=title)) def forum(request, pk): """Listing of threads in a forum.""" threads = Thread.objects.filter(forum=pk).order_by("-created") threads = mk_paginator(request, threads, 20) forum = Forum.objects.get(pk=pk) return render_to_response("forum/forum.html", add_csrf(request, threads=threads, pk=pk, forum=forum)) def thread(request, pk): #def thread(request,forum_pk, pk): """Listing of posts in a thread.""" posts = Post.objects.filter(thread=pk).order_by("created") posts = mk_paginator(request, posts, 15) t = Thread.objects.get(pk=pk) f = Forum.objects.get(pk=t.forum.pk) return render_to_response("forum/thread.html", add_csrf(request, posts=posts, pk=pk, forum_title=f.title, thread_title=t.title, forum_pk=t.forum.pk, media_url=MEDIA_URL)) def add_csrf(request, **kwargs): """Add CSRF to dictionary.""" d = dict(user=request.user, **kwargs) d.update(csrf(request)) return d @login_required def new_thread(request, pk): """Start a new thread.""" p = request.POST if p["subject"] and p["body"]: forum = Forum.objects.get(pk=pk) thread = Thread.objects.create(forum=forum, title=p["subject"], creator=request.user) Post.objects.create(thread=thread, title=p["subject"], body=p["body"], creator=request.user) increment_post_counter(request) return HttpResponseRedirect(reverse("forum.views.forum", args=[pk])) @login_required def reply(request, pk): """Reply to a thread.""" p = request.POST if p["body"]: thread = Thread.objects.get(pk=pk) post = Post.objects.create(thread=thread, title=p["subject"], body=p["body"], creator=request.user) return HttpResponseRedirect(reverse("forum.views.thread", args=[pk]) + "?page=last") def increment_post_counter(request): profile = request.user.userprofile_set.all()[0] profile.posts += 1 profile.save() @login_required def new_thread(request, pk): """Start a new thread.""" p = request.POST if p["subject"] and p["body"]: forum = Forum.objects.get(pk=pk) thread = Thread.objects.create(forum=forum, title=p["subject"], creator=request.user) Post.objects.create(thread=thread, title=p["subject"], body=p["body"], creator=request.user) increment_post_counter(request) return HttpResponseRedirect(reverse("forum.views.forum", args=[pk])) def mytest(request, pk): profile = UserProfile.objects.get(user=pk) p = request.POST return render_to_response("forum/mytest.html", {}) class ForumAdmin(admin.ModelAdmin): pass
Forum app urls:
from django.conf.urls import patterns, include, url urlpatterns = patterns('forum.views', url(r'^$', 'main'), url(r'^forum/(\d+)/$', 'forum'), url(r'^thread/(\d+)/$', 'thread'), url(r"^post/(new_thread|reply)/(\d+)/$", "post"), url(r"^reply/(\d+)/$", "reply"), url(r"^new_thread/(\d+)/$", "new_thread"), url(r"^profile/(\d+)/$", "profile"), url(r"^save_profile/(\d+)/$", "save_profile"), url(r"^mytest/(\d+)/$", "mytest"), )
Registration app urls:
from django.conf.urls import patterns, include, url urlpatterns = patterns('registration.views', url(r'^logout/$', 'logout'), url(r'^login/$', 'login'), url(r'^register/$', 'register_user'), url(r'^register_success/$', 'register_success'), url(r'^auth/$', 'auth_view'), url(r'^loggedin/$', 'loggedin'), url(r'^invalid/$', 'invalid_login'), )
Project urls:
from django.conf.urls import patterns, include, url from django.contrib import admin admin.autodiscover() urlpatterns = patterns('', # user authentication urls #url(r'^accounts/', include('djangoapp.urls')), # user registration urls url(r'^accounts/', include('registration.urls')), # admin url(r'^admin/', include(admin.site.urls)), # blog url(r'^blogs/', include('blog.urls')), # forum url(r'^forums/', include('forum.urls')), # django default url(r'^$', 'djangoapp.views.home', name='home'), # myapp - image upload url(r'^myapp/', include('myapp.urls')), )
from django.shortcuts import render_to_response from django.http import HttpResponseRedirect from django.contrib import auth from django.core.context_processors import csrf from django.contrib.auth.forms import UserCreationForm # Create your views here. def logout(request): auth.logout(request) return render_to_response("logout.html") def login(request): c = {} c.update(csrf(request)) return render_to_response("login.html", c) def auth_view(request): ''' username = request.POST.get('username', '') password = request.POST.get('password', '') ''' username = request.POST['username'] password = request.POST['password'] user = auth.authenticate(username=username, password=password) if user is not None: auth.login(request, user) return HttpResponseRedirect('/dj/accounts/loggedin') else: return HttpResponseRedirect('/dj/accounts/invalid') def loggedin(request): return render_to_response('loggedin.html', {'full_name': request.user.username}) def invalid_login(request): return render_to_response('invalid_login.html') def register_user(request): # 2nd time around if request.method == 'POST': # form = MyRegistrationForm(request.POST) form = UserCreationForm(request.POST) if form.is_valid(): form.save() return HttpResponseRedirect('/dj/accounts/register_success') # 1st time visit args = {} args.update(csrf(request)) # form with no input # args['form'] = MyRegistrationForm() args['form'] = UserCreationForm() print args # passing form return render_to_response('register.html', args) def register_success(request): return render_to_response('register_success.html')
All files including templates are available: forum.tar.gz and registration.tar.gz.
Ph.D. / Golden Gate Ave, San Francisco / Seoul National Univ / Carnegie Mellon / UC Berkeley / DevOps / Deep Learning / Visualization