Tworzymy mikroblog, ścianę użytkownika w Django - część 1

Facebook jest światowym liderem w branży portali społecznościowych. Zastosowany w nim interfejs użytkownika jest powszechnie znany i powielany w innych aplikach. Projektując nowe aplikacje społecznościowe, czy też intranety, aplikacje do pracy grupowej możemy wykorzystać zawarte w Facebooku pomysły - takie jakie "Ściana" użytkownika. Łącząc możliwości mikrobloga z rozbudowaną obsługą multimediów i obsługą znajomości pomiędzy użytkownikami otrzymamy własną aplikację tego typu.

djang-wall

Załóżmy że chcemy stworzyć aplikację dla firmy, lub organizacji złożonej z kilku działów. Chcemy dać możliwość publikowania wiadomości dla znajomych, pracowników z działu, czy też wiadomości publicznych i prywatnych. Do tego proste załączanie multimediów - link do strony, grafiki, czy strony z klipem video i "gotowe" dla użytkownika. Zacznijmy od profilu użytkownika:

class Department(models.Model):
	"""
	Department model
	"""
	title = models.CharField(max_length=255, verbose_name=_('Name'), blank=True)
	slug = models.SlugField(max_length=255, unique=True, verbose_name=_('Slug'))
	def __str__(self):
		return self.title
	def __unicode__(self):
		return self.title
	class Meta:
		verbose_name = _('Department')
		verbose_name_plural = _('Departments')


class Profile(models.Model):
	"""
	User Profile
	"""
	user = models.ForeignKey(User, unique=True, verbose_name=_('User'), related_name='user')
	image = models.ImageField(upload_to='user_images', verbose_name=_('Avatar'), blank=True, null=True)
	dep = models.ForeignKey(Department, verbose_name=_('Department'))
	friends = models.ManyToManyField(User, verbose_name=_('Friends'), blank=True, null=True, related_name='friends')
	last_visit = models.DateTimeField(blank=True, auto_now=True, verbose_name=_('Last visit'))
	
	# random profile stuff
	public_email = models.EmailField(max_length=100, verbose_name=_('Public email'), blank=True, null=True)
	facebook = models.URLField(max_length=100, verbose_name=_('Facebook profile'), blank=True, null=True)
	goldenline = models.URLField(max_length=100, verbose_name=_('Goldenline profile'), blank=True, null=True)
	jabber = models.CharField(max_length=100, verbose_name=_('Jabber/GTalk'), blank=True, null=True)
	skype = models.CharField(max_length=100, verbose_name=_('Skype'), blank=True, null=True)
	gg = models.IntegerField(verbose_name=_('Gadu Gadu'), blank=True, null=True)
	bio = models.TextField(verbose_name=_('Bio'), blank=True, null=True)
	
	def __str__(self):
		return str(self.user)
	def __unicode__(self):
		return unicode(self.user)
	class Meta:
		verbose_name = _('User Profile')
		verbose_name_plural = _('User Profiles')

Mamy dwa modele - model działów (Department) oraz właściwy model będący profilem użytkownika. Ważne pola to przypisanie do działu oraz lista znajomych. Należy zadbać o generowanie profilu użytkownika przy rejestracji bądź logowaniu, ale to już sprawa poza obszarem tego artykułu.

Przejdźmy teraz do modelu wiadomości. Na początek prosty model multimediów:
class Media(models.Model):
    """
    storage for Blip attached medias
    """
    TYPES = [('url', _('URL')), ('image', _('Image')), ('video', _('Video Clip'))]
    type = models.CharField(max_length=100, verbose_name=_('Type'), choices=TYPES)
    url = models.CharField(max_length=255, verbose_name=_('Media URL'))
    text = models.TextField(verbose_name=_('Parsed media text'))
    class Meta:
        verbose_name = _('Media')
        verbose_name_plural = _('Media')
    def __str__(self):
        return self.url
    def __unicode__(self):
        return self.url

Mamy prosty model zawierający pole "type", które określa nam typ treści dla danego obiektu, url wprowadzony przez użytkownika i pole "text" zawierające gotowy do wyświetlenia na stronie kod HTML (np. tytuł i opis strony www, kod HTML wyświetlający klip video itd.). Generowanie gotowego kodu odbywać powinno się po wprowadzeniu przez użytkownika odnośnika - AJAXem przesyłamy adres URL do widoku parsującego, który zwróci gotowy tekst i stworzy wstępnie obiekt "Media", który dołączony zostanie do wiadomości.

Główny element aplikacji to model wiadomości. Na chwilę obecną model wiadomości wygląda tak:
class Blip(models.Model):
    """
    storage for user wall messages
    """
    TYPES = [('f', _('Friends')), ('d', _('All from my department')), ('a', _('All users')), ('p', _('Private message'))]
    
    message = models.TextField(verbose_name=_('Message'))
    author = models.ForeignKey(User, verbose_name=_('Author'), related_name='author')
    # user department
    dep = models.ForeignKey(Department, verbose_name=_('Department'))
    
    privacy_type = models.CharField(max_length=100, verbose_name=_('Privacy'), choices=TYPES)
    # set this if private msg
    private_recipients = models.ManyToManyField(User, verbose_name=_('Recipients of PM'), blank=True, null=True, related_name='private_recipients')
    # copy from profile if message for friends
    message_friends = models.ManyToManyField(User, verbose_name=_('Friends at the time of creation'), blank=True, null=True, related_name='message_friends')
	# if this is a reply to another Blip
    in_reply_to = models.ForeignKey('self', verbose_name=_('Reply to blip'), blank=True, null=True)
    
    date = models.DateTimeField(auto_now_add=True)
    
    media = models.ManyToManyField(Media, verbose_name=_('Media'), blank=True, null=True)
    class Meta:
        verbose_name = _('Blip')
        verbose_name_plural = _('Blips')
    def __str__(self):
        return self.message
    def __unicode__(self):
        return self.message

Mamy pole "message" na tekst wprowadzony przez użytkownika, pole "author" określające autora wpisu oraz pola kopiujące dane z jego profilu: "dep" i "message_friends". Kopiowanie tych danych ułatwi później wybieranie wiadomości na ścianie użytkownika. Pole "privacy_type" określa poziom prywatności wpisu. Jeżeli użytkownik wybierze wiadomość prywatną to trzeba będzie wyświetlić listę znajomych tak by mógł wybrać odbiorców wiadomości - zapisanych następnie w "private_recipients". Pole "in_reply_to" pozwala nam zastosować ten model do komentarzy pod wiadomościami. Główne wiadomości nie będące odpowiedziami będą miały wartość null.

Mając model pomyślmy o podstawowej operacji - wyświetlaniu "pasując" wiadomości. Użytkownik może widzieć swoje wiadomości, a także wiadomości publiczne, wiadomości z jego działu, wiadomości od jego przyjaciół, oraz wiadomości prywatne skierowane do niego. Mając powyższy model łatwo można napisać odpowiednie zapytanie z pomocą operatora Q z django.db.models:

@login_required
def show_wall(request):
    """
    Show the wall page
    """
    p = request.user.get_profile()
    
    blips = Blip.objects.filter(
                        # user blips
                        Q(author=request.user, in_reply_to__isnull=True) |
                        # blips for user department
                        Q(privacy_type='d', dep=p.dep, in_reply_to__isnull=True) |
                        # private blips to this user
                        Q(privacy_type='p', private_recipients=request.user, in_reply_to__isnull=True) |
                        # blips from user friends
                        Q(privacy_type='f', message_friends=request.user, in_reply_to__isnull=True) |
                        # public blips
                        Q(privacy_type='a', in_reply_to__isnull=True)
                    ).order_by('-date')
    
    return render_to_response('wall/show_wall.html', {'blips': blips}, context_instance=RequestContext(request))
Szybka ścieżka od strony kodu Pythona okazała się dość krótka. "Niestety" w przypadku takich aplikacji najwięcej daje i najwięcej zależy od interfejsu. Nie ma gotowych zestawów, czy szablonów, ale można złożyć kilka różnych wtyczek do kupy: W następnej części zajmę się właśnie interfejsem i dodatkowymi widokami obsługującymi AJAXowe żądania.
djang-wall
RkBlog

Django, 20 May 2010

Comment article
Comment article RkBlog main page Search RSS Contact