Open source repositories at: github and bitbucket. Current project: erp4all

Monday, September 3, 2012

pCMS - Ένα πολύ μικρό CMS σε django

pCMS - Ένα πολύ μικρό CMS σε django

Η django  συνοδεύεται από ένα πολύ χρήσιμο module το django.contrib.flatpages. Αυτό είναι υπεύθυνο γιατί την προβολή περιεχομένου που είναι αποθηκευμένο στην βάση. Είναι πολλές φορές που σελίδες δεν χρειάζεται να είναι δυναμικές γιατί το περιεχόμενο τους δεν αλλάζει όπως για παράδειγμα η άδεια χρήσης ή οι όροι και προϋποθέσεις ή στατικές πληροφορίες επιχείρησης (διεύθυνση, τηλέφωνο κλπ). Σε αυτές τις περιπτώσεις αυτό το module είναι πολύ χρήσιμο και εύκολο στην χρήση του.

Μπορούμε όμως να το πειράξουμε λίγο, να προσθέσουμε και μερικά ακόμα χαρακτηριστικά και να το κάνουμε ένα πολύ μικρό σύστημα διαχείρισης περιεχομένου (CMS). Θα προσθέσουμε τις εξής δυνατότητες σε κάθε σελίδα:
  • Πεδίο αν είναι δημοσιευμένη ή όχι
  • Πεδία για τα meta tags: description, keywords
  • Πεδία για προσθήκη javascript και css
Χρησιμοποιώντας και ένα δεύτερο module της django, το django.contrib.comments μπορούμε να προσθέσουμε σχόλια σε όποια/ες σελίδα/ες θέλουμε.

Θέλουμε όμως να υπάρχει και η δυνατότητα να εμφανίζουμε links με τις διευθύνσεις των σελίδων μας αυτόματα. Να μην αλλάζουμε το uls.py κάθε φορά που βάζουμε μια καινούργια ή αφαιρούμε μια παλιά. Θα ήταν ωραίο να εμφανίζουμε μενού με τις σελίδες που αναφέρονται σε ένα θέμα ομαδοποιημένες. Οι σελίδες για τα προϊόντα κάτω από ένα μενού, οι σελίδες των υπηρεσιών κάτω από άλλο μενού.

Προσθέτουμε και ένα απλό σύστημα αναζήτησης στις σελίδες μας και έχουμε το pico CMS. 

Ακολουθεί μια σύντομη περιγραφή των αλλαγών που χρειάστηκαν να γίνουν στο αρχικό module ώστε να εμπλουτιστεί με τα νέα στοιχεία.

Ξεκινώντας από τα μοντέλα θα δηλώσουμε ένα δικό μας με όνομα Flatpage και το οποίο θα κληρονομεί από το FlatPage της django.

from django.contrib.flatpages.models import FlatPage

class Flatpage(FlatPage):
    published = models.BooleanField(verbose_name=u"Published", default=True, blank=True)
    scripts = models.TextField(verbose_name=u"Scripts", blank=True)
    styles = models.TextField(verbose_name=u"Styles", blank=True)
    keywords = models.TextField(verbose_name=u"Keywords", blank=True)
    description = models.TextField(verbose_name=u"Description", blank=True)
Έχουμε έτοιμες τις επιπλέον πληροφορίες που θέλουμε για κάθε σελίδα. Θέλουμε όμως να εμφανίζονται μόνο αυτές που έχουν published==True. Το views.py όμως που υπάρχει με το django.contrib.flatpages τις δείχνει όλες. Το αντιγράφουμε και προσθέτουμε ένα τον όρο published=True στο get_object_or_404:


def flatpage(request, url):
    if not url.startswith('/'):
        url = '/' + url
    try:
        f = get_object_or_404(Flatpage, url__exact=url, sites__id__exact=settings.SITE_ID, published=True)
    except Http404:
        if not url.endswith('/') and settings.APPEND_SLASH:
            url += '/'
            f = get_object_or_404(Flatpage, url__exact=url, sites__id__exact=settings.SITE_ID, published=True)
            return HttpResponsePermanentRedirect('%s/' % request.path)
        else:
            raise
    return render_flatpage(request, f)

Για να ενεργοποιηθεί το δικό μας view και όχι του django.contrib.flatpages δεν χρησιμοποιούμε το δικό του middleware αλλά ένα δικό μας, αντιγραφή του προηγούμενου, αλλά αντί να κάνει import το django.contrib.flatpages.views, να κάνει το δικό μας.

Υπάρχει και template tag που έρχεται με το αρχικό module αλλά κι αυτό χρειάζεται να μετατραπεί ώστε να δείχνει μόνο τις δημοσιευμένες σελίδες.

Αυτές είναι και οι αλλαγές που χρειάζονται για εμπλουτίσουμε λίγο τις στατικές σελίδες.

Η δημιουργία των μενού γίνεται στο admin περιβάλλον.  Δίνοντας τον τίτλο του μενού και το url του. Το μενού θα φτιαχτεί σαν dropdown και θα περιέχει κάθε σελίδα που το url της αρχίζει με το url του μενού. Έτσι για παράδειγμα ένα μενού με url /services/ θα περιέχει όλες τις σελίδες που ξεκινάνε με /services/ όπως: /services/consulting/, /services/developing/.
Δεν υπάρχει όμως η δυνατότητα να έχουμε υπό μενού.

Τα μενού όπως και οι σελίδες έχουν την δυνατότητα να εμφανίζονται μόνο σε χρήστες που έχουν κάνει login. 

Προσθέσαμε και μια απλοποιημένη έκδοση του django.contrib.comments, όπου για να αποφύγουμε θέματα ασφάλειας και διαχείρισης, μόνο οι χρήστες που έχουν κάνει login μπορούν να σχολιάσουν σε μια σελίδα. Ο καθένας μπορεί να σβήσει τα δικά του σχόλια όποτε θέλει και ο admin μπορεί να τα σβήσει όλα.

Admin περιβάλλον

Καλά όλα αυτά αλλά να γράψεις html με το χέρι για να δημιουργήσεις σελίδες είναι λίγο χρονοβόρο. Υπάρχουν όμως έτοιμοι html editors που μπορούμε να χρησιμοποιήσουμε. Εδώ έχουμε χρησιμοποιήσει τον tinymce. Στον static φάκελο δημιουργούμε έναν νέο με το όνομα admin. Εκεί αποσυμπιέζουμε το tinymce. Δημιουργούμε ένα αρχείο textareas.js το οποίο θα είναι υπεύθυνο για την εκκίνηση του editor. Στον ορισμό του admin interface για την εφαρμογή μας (admin.py) ορίζουμε τα εξής:
    class Media:
        js = ('admin/tiny_mce.js','admin/textareas.js')
Έχουμε έτσι έναν rich text editor για να δημιουργήσουμε τις σελίδες μας. Εκτός όμως από αυτό έχουμε συνδέσει και έναν γραφικό filemanager, τον elFinder ώστε να μπορούμε να ανεβάζουμε εικόνες, αρχεία javascript και css. Έτσι γίνεται πιο εύκολη η δημιουργία των στατικών σελίδων.

Οι σελίδες χρησιμοποιούν σαν template το αρχείο fpd_default.html όταν δεν έχουν την δυνατότητα σχολίων και το fpg_comments.html όταν την έχουν. Μπορείτε να φτιάξετε ένα δικό σας αρχείο και να το ορίσετε σαν το template της σελίδας.

Αυτά είναι τα κύρια χαρακτηριστικά και οι βασικές τροποποιήσεις του pCMS. Κατεβάστε το από το github και πειραματιστείτε μαζί του. Απαιτείται μόνο η django 1.4.1 για να λειτουργήσει. Το βασικό module είναι το fpg και μπορεί να χρησιμοποιηθεί όπως είναι σε οποιοδήποτε άλλο project με ελάχιστες τροποποιήσεις.

Καλό hacking.

Andreas Porevopoulos
Lead Developer at
http://www.dream-solutions.gr



Wednesday, August 29, 2012

Ένα Twitter clone γραμμένο σε django

Ένα Twitter clone γραμμένο σε django

To dwitter είναι ένα εκπαιδευτικό project σε django. Ο σκοπός του είναι να παρουσιάσει διάφορες τεχνικές και προγράμματα που είναι διαθέσιμα για djangο. Με τον συνδυασμό αυτών και λίγο προγραμματισμό είμαστε σε θέση να φτιάξουμε μια εφαρμογή με τις δυνατότητες του twitter.

Ξεκίνησε σαν ένα project για εκμάθηση των δυνατοτήτων της socket.io. Αυτή είναι μια βιβλιοθήκη γραμμένη σε javascript που υλοποιεί πρωτόκολλα websockets στους διάφορους browser. Με τον όρο websockets αναφερόμαστε γενικά στην δυνατότητα που έχει ένας server να επικοινωνήσει απ' ευθείας με τους clients του μέσω http. Έτσι ενώ εσείς διαβάζετε τα tweets που έχετε στην σελίδα σας, ένα μήνυμα στο πάνω μέρος της σελίδας σας ενημερώνει ότι: "Υπάρχουν 2 νέα tweets". Πως ήρθε αυτό το μήνυμα σε εσάς, αφού δεν έχετε κάνει refresh ή κάποια άλλη ενέργεια; Πως σας ξεχώρισε ανάμεσα από τόσους που είναι συνδεδεμένοι την ίδια στιγμή; Για όλα αυτά είναι υπεύθυνα τα websockets. Για την υλοποίηση των websockets σε python και django χρησιμοποιήθηκε το gevent-socketio. Ένα module βασισμένο στο gevent, το οποίο παρέχει και έναν ειδικό worker για το gunicorn. Το setup αυτό λειτουργεί πολύ καλά με websockets, εξυπηρετεί τις σελίδες της django και ακόμα μοιράζει τα static αρχεία μας.

Ο χρήστης μετά το login μεταφέρεται στην σελίδα με τα dwits (ταιριαστή παράφραση του tweets!) του. Η σελίδα ζητάει από τον server μια σύνδεση socket και τον πληροφορεί ότι θέλει να συμμετάσχει στο δωμάτιο με το όνομα του χρήστη. Όπως στο irc υπάρχουν πολλά rooms (δωμάτια), έτσι κι εδώ κάθε χρήστης έχει το δικό του. Σε αυτό το δωμάτιο ακούει για νέα dwits. Όταν ένας χρήστης δημοσιεύει ένα dwit, αυτό αντιγράφεται σε όλα τα δωμάτια των χρηστών που τον ακολουθούν. Ο server τώρα, όταν έχει κάτι να στείλει από ένα δωμάτιο το στέλνει μέσω του socket που έχει εγγραφεί σε αυτό το δωμάτιο. Έτσι ο χρήστης λαμβάνει μόνο τα dwits που προορίζονται γι' αυτόν. Το ίδιο συμβαίνει και όταν ο χρήστης παρακολουθεί κάποιο tag. Φεύγει από το δικό του δωμάτιο και συνδέεται σε δωμάτιο με το όνομα του tag. Κάθε dwit που δημοσιεύεται και περιέχει tag, αντιγράφεται και σε δωμάτιο με το όνομα του tag.


Από τα παραπάνω είναι φανερό ότι χρειάζεται μια ειδική αντιμετώπιση όσον αφορά στην διαχείριση των dwits. Αυτά αποθηκεύονται στην βάση της django, αλλά για τον διαμοιρασμό τους χρησιμοποιούμε την πολύ γρήγορη redis και συγκεκριμένα την δυνατότητά της pub/sub. Με αυτήν δίνεται η δυνατότητα σε μια function να εγγραφεί (subscribe) σε ένα "κανάλι". Όταν κάποιος δημοσιεύσει (publish) σε αυτό το κανάλι τότε η function εκτελείται με τα δεδομένα της δημοσίευσης. Έτσι το dwit σώζεται στην βάση και αντιγράφεται στα ανάλογα κανάλια (ακόλουθοι και tags). Όποιο κανάλι έχει συνδρομητές (sockets) τους αποστέλλεται το περιεχόμενο και αυτοί με την σειρά τους το στέλνουν στην σελίδα του χρήστη.


Κάνοντας λίγες πράξεις βλέπουμε ότι αυτό το πλάνο θα έχει πρόβλημα αν εκτελείται σειριακά. Αν ο κάθε χρήστης έχει Χ ακόλουθους και το κάθε dwit μπορεί να έχει Υ tag και να δημοσιεύονται ταυτόχρονα Ζ dwits, o πολλαπλασιασμός αυτός γρήγορα θα φτάσει σε σημείο όπου η django δεν θα προλαβαίνει να δείξει σελίδες. Γι' αυτό, το κομμάτι αυτό, το βάζουμε σε μια ουρά. Κάθε τι που είναι να δημοσιευθεί, θα δημοσιευθεί με την σειρά που μπήκε στην ουρά και θα περιμένει όση ώρα χρειαστεί, αφήνοντας την εφαρμογή μας να μοιράζει σελίδες. Αυτήν την δουλειά κάνει το celery. Κάθε function που δηλώνεται ως task του celery, μεταφέρεται στην ουρά (στην προκειμένη περίπτωση πάλι στην redis) και το celery θα την εκτελέσει όταν μπορέσει. Υπάρχει μεγάλος φόρτος εργασίας; Ξεκινάμε περισσότερους εργάτες (workers) του celery.


Η βάση της django έχει τρία βασικά μοντέλα. Τους χρήστες της εφαρμογής, τα dwits και τα dwits per follower (dwits ανά ακόλουθο). Στο τελευταίο μοντέλο για κάθε dwit που δημοσιεύει ένας χρήστης δημιουργούνται εγγραφές με το id του dwit και το id του κάθε ακόλουθου του χρήση. Έτσι αν κάποιος έχει 1000 ακόλουθους και έχει κάνει 1000 dwits θα έχει 1 εκ. εγγραφές σε αυτό το μοντέλο. Μόνο αυτός. Αν έχεις μόνο (για τα δεδομένα του twitter) 1000 τέτοιους χρήστες έφτασες εύκολα 1 δισ. εγγραφές. Φαίνεται αμέσως ότι μια κλασική βάση δεδομένων θα έχει πρόβλημα. Γι' αυτό θα ήταν καλό να χρησιμοποιηθεί μια NoSQL βάση σαν την redis (σχετικό άρθρο εδώ). Από την άλλη όμως αυτό είναι ένα απλό project και δεν συναγωνίζεται το twitter. Να αναφέρουμε εδώ και τις εκπληκτικές δυνατότητες του django ORM, όπου για παράδειγμα στο μοντέλο των χρηστών υπάρχει ένα πεδίο ManyToMany με τον εξής ορισμό:


following = models.ManyToManyField('self', blank=True,\
                   symmetrical=False, related_name='followers')
Αυτό το πεδίο δείχνει ποιους χρήστες ακολουθεί ο κάθε χρήστης. Κάνοντας:

member = Member.objects.get(pk=10)
following = member.following.all()
έχουμε τους χρήστες που ακολουθεί ο χρήστης με id=10. Αλλά κάνοντας και:

followers = member.followers.all()
έχουμε όλους που τον ακολουθούν. Χωρίς να γράψουμε άλλο κώδικα, εύκολα και ευανάγνωστα. Κάτι τέτοιο δεν υπάρχει σε NoSQL.

Για την εγγραφή των χρηστών χρησιμοποιήσαμε μια δικιά μας έκδοση του django-registration και για την αυθεντικοποίηση των χρηστών μια ελαφρώς αλλαγμένη έκδοση του django.contrib.auth. Το δεύτερο έχει αντιγραφεί όπως είναι από το source (εκτός από κάτι αλλαγές στις μεταφράσεις και στην φόρμα login) και προσθέσαμε την δυνατότητα όταν στέλνει email, να το στέλνει σαν task στο celery. Με αυτό τον τρόπο δεν περιμένει να τελειώσει η αποστολή και μετά να επιστραφεί ο έλεγχος στην django. Το ίδιο κάναμε και στο registration. Αυτός είναι και ο πιο διαδεδομένος τρόπος χρήσης του celery. Για δουλειές που δεν υπεύθυνος ο server της εφαρμογής. Όπως παρακάτω με το haystack.


Το haystack είναι ένα module αναζήτησης. Προσφέρει όλες τις απαραίτητες συναρτήσεις για την αναζήτηση στοιχείων σε μια βάση, χρησιμοποιώντας διάφορες μηχανές αναζήτησης διαθέσιμες στην python. Η αναζήτηση δεν γίνεται απ' ευθείας στην βάση αλλά σε ένα δικό του αρχείο στο οποίο έχει αποθηκεύσει τα στοιχεία που του έχουμε ορίσει ότι είναι αναζητήσιμα. Αυτό το αρχείο πρέπει να ενημερώνεται σε τακτά χρονικά διαστήματα με τα καινούργια στοιχεία της βάσης. Γι αυτόν τον σκοπό χρησιμοποιούμε μια άλλη δυνατότητα του celery, την δυνατότητα προγραμματισμού (crontab) εργασιών. Έχουμε ορίσει μια συγκεκριμένη συνάρτηση, που πραγματοποιεί την ενημέρωση, να εκτελείται κάθε 15 λεπτά.


Για την διαχείριση των tag χρησιμοποιήσαμε το django-taggit και για την εμφάνιση της εφαρμογής το bootstrap. Αυτό είναι ένα πολύ ωραίο css framework, γραμμένο από τα παιδιά του twitter και η προσθήκη δικών μας css είναι ελάχιστη. Χρησιμοποιώντας τα media queries, το bootstrap προσαρμόζεται αυτόματα σε κινητές και pad συσκευές.


Το dwitter είναι διαθέσιμο στο github και περιμένει περισσότερο πειραματισμό και hacking. Οδηγίες εγκατάστασης και παραμετροποίησης βρίσκονται στο README και στο wiki.Υπάρχει και ένα live demo site στην διάθεση σας για να δείτε την λειτουργία του. Μπορείτε να δημιουργήσετε δύο χρήστες με το ίδιο email (κανονικό ή ψεύτικό, δεν συγκεντρώνουμε email) και να κάνετε login από δύο διαφορετικούς browser (firefox και chrome ή desktop και κινητό) και να δείτε την μετάδοση των dwit σε πραγματικό χρόνο από τον ένα στον άλλο.


Καλό hacking.

Andreas Porevopoulos
Lead developer at
http://www.dream-solutions.gr