Writing a Gallery App in Django, Part I

OnethingthatIseetheneedforonalmosteverysiteisaplacetoputimages.Whetherit’sabandwebsite,apersonalhomepage,oraschoolnewspaper,therewillbeaneedforaphotogallery.TheeasywaytodothatistouseanopensourcepackageavailablealreadylikeGallery2.Butthat’swritteninPHPanditwon’tintegrateeasilywiththerestofyoursite–especiallyifyouuseDjangoastheframeworkfortherestofyoursite.

Thesolution:writeagalleryapplicationforuseinyourwebsite.Atfirstitmayseemlikeadauntingtasktocreate,butasI’vefoundout,itcanbequiteeasy.Myimplementationisnotcompletelyupandrunningyet,butthat’sduetodesignissueswiththerestofthesite,notthephotogalleryappitself.Withnofurtheradieu,let’sdiveinandseehowthiscanwork.

class Album(models.Model):
    name = models.CharField(maxlength=128)
    slug = models.SlugField(prepopulate_from=("name",))
    summary = models.TextField()
    date_created = models.DateTimeField(auto_now_add=True)
    date_modified = models.DateTimeField(auto_now=True)

Asyoucansee,thisistheAlbumobjectwhichcontainsinformationaboutasetofassociatedphotos.Butwait,wedon’thavephotoscreatedyet!Let’sdothatnow.

class Photo(models.Model):
    title = models.CharField(maxlength=256)
    summary = models.TextField(blank=True, null=True)
    date_created = models.DateTimeField(auto_now_add=True)
    date_modified = models.DateTimeField(auto_now=True)
    image = models.ImageField(upload_to='photos/%Y/%m')
    album = models.ForeignKey(Album)
    is_cover_photo = models.BooleanField()

Oksonowwehavealbumswhichhavephotos,andphotoshavealotofinformationlikeafilewhichcontainsanimage,atitle,andasummary.

Nowthatwehavetheseobjects,wehavesomechoicestomake.Theimagesneedtoberesizedintomediumandsmallimagesfordisplayonthelistanddetailpages,respectively.Thiscanbedoneseveralways:

UsetheHTMLwidthandheightattributestoresizetheimages.

CreateaDjangoviewwithwidthandheightparameterstobothresizeandservetheimages.(Lazycomputation)

Resizetheimagesonuploadandstorethemtodisk.(Upfrontcomputation)

Thefirstwayisnotoptimalfortworeasons.Firstlyeachpicturemustbedownloadedinit’sentirety.Thisisinefficientandcouldupsetpeoplewithlessbandwidthorbandwidthlimits.Secondly,whenmostbrowsersresizeimages,theydosousingpoorqualityfilters,resultinginalowqualityrepresentationofanimage.

Thesecondwayisthemostflexible,sincetheheightandthewidthcanbechangedinatemplateorinaviewandtheresizedimageswillchangeaccordingly.However,thereismoreon-the-flycomputationwiththisway,possiblyincreasingpageloadtimes.Also,Djangoisnotdesignedtobeusedtoservebinaryfilesdirectly,sotherecouldbeunforseenconsequenceswithhandlingalargenumberofphotosthisway.

Thethirdwayislessflexible,butithasonekeyadvantage:it’sfast.Sincethecomputationisdoneonupload,Apacheoranyotherhttpmediaservercanbeusedinstead,completelyremovingtheneedtousetheDjangoframeworkatall.Let’simplementitthisway.

First,we’llneedtooverloadthesavefunctionofthePhotomodel:

def save(self):
    if self.is_cover_photo:
        other_cover_photo = Photo.objects.filter(album=self.album).filter(is_cover_photo = True)
        for photo in other_cover_photo:
            photo.is_cover_photo = False
            photo.save()
    filename = self.get_image_filename()
    if not filename == '':
        img = Image.open(filename)
        img.thumbnail((512,512), Image.ANTIALIAS)
        img.save(self.get_medium_filename())
        img.thumbnail((150,150), Image.ANTIALIAS)
        img.save(self.get_small_filename())
    super(Photo, self).save()

BeforeItalkaboutthethumbnailingaspectofthisfunction,I’dliketobrieflyexplainwhat’sgoingonwiththecover_photoaspectofPhotoobjects.EachAlbumhasacoverphoto,soiftheAlbumneedstoberepresented,itcanberepresentedbyonespecialphoto.ThisisjustpartofthewaythatIhavedesignedmyobject,andcaneasilyberemoved.However,thereisasmallbitofobligatoryboilerplatecodeinthissavefunctionwhichsetsanyotheris_cover_photoattributestoFalseifnecessary.(Therecanonlybeonecoverphotoperalbum,afterall).I’llcomebacktodealingwithcoverphotoslater.

Firstoff,theifnotfilename==”statementisneededbecausesaveissometimescalledwithnoimagedata,andthatcanandwillthrowexceptionsifPILisusedonaNoneobject.Then,itresizesamedium-sized(512pxby512px)imageandsavesittoalocationprovidedbyget_medium_filename.Ileaveittoyoutodefineyourownget_medium_filenameandget_small_filenamewithyourownnamingconvention.IfollowedFlickr’sexampleofanunderscorefollowedbyanargument(image001.jpgbecomesimage001_m.jpgformediumandimage001_s.jpgforsmall).Finally,thismethodmustcalltheit’sparentsavemethodsothatalloftheotherattributesaresavedcorrectly.

Nowthatwe’veoverloadedthesavefunctionality,wearegoingtohaveaproblemwithdeletion.Djangowillautomaticallydeletetheoriginalimage,buttheresizedthumbnailswillbeleftonthediskforever.Thisisnotahugeproblem,butwedon’twantthattohappenanyways.Solet’stakecareofthatbyalsooverloadingthedeletefunctionofPhoto’smodel:

def delete(self):
    filename = self.get_image_filename()
    try:
        os.remove(self.get_medium_filename())
        os.remove(self.get_small_filename())
    except:
        pass
    super(Photo, self).delete()

Simplyput,itdeletesthethumbnailfilesandthencallsit’sparent’sdelete,whichwillinturndeleteit’soriginalfile.

Asidefromcreatingsomeoptionalhelperfunctionslikeget_small_image_urland/orget_medium_image_url,there’snotmuchmoretobedonewiththePhotomodel.Whatcanbedonestill,however,isinAlbum.WenowhavezerooronecoverphotosforeachAlbum,butit’sgoingtobetrickytoqueryforthiseachtime,solet’screateafunctioninAlbumtohelpusretrievetheassociatedcover_photoPhotoobject:

def get_cover_photo(self):
    if self.photo_set.filter(is_cover_photo=True).count() > 0:
        return self.photo_set.filter(is_cover_photo=True)[0]
    elif self.photo_set.all().count() > 0:
        return self.photo_set.all()[0]
    else:
        return None

Thatis,iftheAlbumhasaphotowithis_cover_photo==True,thengrabit,otherwisegrabthefirstimage.Iftherearenoimagesinthealbum,returnNone.That’sitforthemodels.Easy,huh?Justrunmanage.pysyncdb,andletDjangodotheheavyliftingforyou.

That’sallforpartoneofthisseriesonwritingagalleryapplicationwithDjango.Nextup:writingtheviews,urlconfs,andputtingitalltogether.Templatingwillbeleftuptoyou,sincetherearesomanywaystodisplaythisinformation,butsomeexampleswillbegiventopointyouintherightdirection.

Notetopurists:Iknowthatsomeofthefunctionalitythatisbeingimplementedashelperfunctionsinthemodelswouldbebetterimplementedascustomtemplatetags,butIfinditeasiertotakealessphilosophicalstanceonthe"right"waytodothingsandsometimesdowhat’smorepractical.Inthiscase,writingmodelfunctionsisamucheasiersolutionthancreatingcompletelynewtemplatetags.Ineithercase,movingtoanewsitewillrequirearewrite,soI’mnotevenconvincedthatithurtsreusability.

相关推荐