2010-03-07 21:51:14 +05:30
from django . db import models
from files . models import File
from fields import HexColorField
from django . contrib . comments . signals import comment_was_posted
import simplejson
from django . core . mail import send_mail
import os
from PIL import Image
from django . template import Template , Context
from django . template . loader import get_template
from settings import MEDIA_ROOT
from django . contrib . auth . models import User
from tagging . fields import TagField
from tagging . models import Tag
def addPx ( val ) :
'''
Adds ' px ' to an integer value for a CSS property from DB
'''
r = str ( int ( val ) ) + " px "
return r
def cleanCSS ( prop ) :
'''
Takes a CSS property and returns it into a value safe for insertion into DB
'''
propStr = str ( prop )
if propStr [ - 2 : ] == ' px ' :
r = int ( propStr [ : - 2 ] )
# elif prop[0] == '#':
# r = prop[1:]
else :
r = prop
return r
def isPx ( s ) :
if s [ : - 2 ] == ' px ' :
return True
else :
return False
def baseFileName ( filename ) :
r = filename . rindex ( ' . ' )
return filename [ 0 : r ]
def extFileName ( filename ) :
r = filename . rindex ( ' . ' ) + 1
return filename [ r : ]
class LinkCategory ( models . Model ) :
title = models . CharField ( max_length = 255 )
is_onfront = models . BooleanField ( default = False )
class Link ( models . Model ) :
product = models . ForeignKey ( " Product " )
category = models . ForeignKey ( " LinkCategory " )
thumbnail = models . ImageField ( upload_to = ' images/link_thumbs/ ' )
description = models . TextField ( blank = True , null = True )
order = models . IntegerField ( )
def __unicode__ ( self ) :
return self . description
class ProductType ( models . Model ) :
name = models . CharField ( max_length = 255 )
aspect_ratio = models . FloatField ( default = 0.707 , help_text = " Default 0.707 as per http://en.wikipedia.org/wiki/Paper_size " )
print_width = models . IntegerField ( help_text = " Unit: mm " )
def __unicode__ ( self ) :
return self . name
class Product ( models . Model ) :
title = models . CharField ( max_length = 255 )
published = models . BooleanField ( default = False )
date_published = models . DateField ( blank = True , null = True )
typ = models . ForeignKey ( " ProductType " )
tags = TagField ( )
creator = models . ForeignKey ( User )
abstract = models . TextField ( blank = True , null = True )
videos = models . ManyToManyField ( " Video " , blank = True )
audios = models . ManyToManyField ( " Audio " , blank = True )
2010-03-25 20:31:51 +05:30
2010-03-07 21:51:14 +05:30
def __unicode__ ( self ) :
return " %s " % ( self . title )
2010-03-25 20:31:51 +05:30
def get_print_multiplier ( self , dpi ) :
# product = Product.objects.get(pk=self.product.id)
print_width_mm = self . typ . print_width
dpm = dpi / / 25.4
pixel_width = print_width_mm * dpm
m = pixel_width / / 800.0
return m
def get_page_list ( self ) :
pages = [ ]
articles = Article . objects . filter ( page = self ) . order_by ( ' order ' )
for a in articles :
a_pages = Page . objects . filter ( article = a ) . order_by ( ' page_no ' )
pages . extend ( a_pages )
return pages
def get_view_size ( self , width = 800 ) :
aspect_ratio = self . typ . aspect_ratio
width = width + .0
height = int ( width / / aspect_ratio )
return ( int ( width ) , height , )
2010-03-07 21:51:14 +05:30
class Video ( models . Model ) :
fil = models . ForeignKey ( File )
srt = models . ManyToManyField ( " Srt " , blank = True , null = True )
def __unicode__ ( self ) :
return self . fil . title
class Audio ( models . Model ) :
fil = models . ForeignKey ( File )
srt = models . ManyToManyField ( " Srt " , blank = True , null = True )
def __unicode__ ( self ) :
return self . fil . title
SRT_LANGS = (
( ' en ' , ' English ' ) ,
( ' ar ' , ' Arabic ' ) ,
)
SRT_TYPES = (
( ' transcript ' , ' Transcript ' ) ,
( ' description ' , ' Description ' ) ,
)
class Srt ( models . Model ) :
fil = models . FileField ( upload_to = " srt/ " )
lang = models . CharField ( max_length = 20 , choices = SRT_LANGS )
typ = models . CharField ( max_length = 40 , choices = SRT_TYPES )
'''
This models holds revisions , gets saved to whenever anything in an article changes - when a box changes , its pretty straightforward what ' s supposed to happen. When a new box or page is created, slightly strange things happen:
For a new box - prop = ' new_box ' , old_val = ' ' , new_val = String JSON representation of the new box .
For new page - prop = ' new_page ' , old_val = ' ' , new_val = String JSON representation of the new page .
For delete box , prop = ' delete_box '
For image_crop , prop = ' crop ' ,
For image_resize , prop = ' image_resize '
2010-03-25 20:31:51 +05:30
( of course , this is ugly ) .
2010-03-07 21:51:14 +05:30
'''
class Revision ( models . Model ) :
article = models . ForeignKey ( " Article " )
page = models . ForeignKey ( " Page " )
box_type = models . CharField ( max_length = 100 )
box_id = models . IntegerField ( )
prop = models . CharField ( max_length = 100 )
old_val = models . TextField ( )
new_val = models . TextField ( )
uuid = models . IntegerField ( )
def saveRevision ( r ) :
page = Page . objects . get ( pk = r [ ' page_id ' ] )
article = page . article
rev = Revision ( article = article , page = page , box_type = r [ ' box_type ' ] , box_id = r [ ' box_id ' ] , prop = r [ ' prop ' ] , old_val = r [ ' old_val ' ] , new_val = r [ ' new_val ' ] , uuid = r [ ' uuid ' ] )
rev . save ( )
return rev . id
class Article ( models . Model ) :
"""
Each page references an article . A single page cannot reference more than one article . The article is what people comment on ( and potentially what audio & video are attached to ) .
"""
name = models . CharField ( max_length = 255 )
product = models . ForeignKey ( " Product " )
order = models . IntegerField ( )
'''
'''
def changes ( self , revision_no , uuid ) :
if int ( revision_no ) == self . current_revision ( ) :
return { ' ok ' : ' ok ' }
else :
d = [ ]
new_revisions_all = Revision . objects . filter ( article = self ) . filter ( id__gt = revision_no )
new_revisions_others = new_revisions_all . exclude ( uuid = uuid )
for rev in new_revisions_others :
2010-03-25 20:31:51 +05:30
#If there are multiple changes on the same property of the same box, send back only the latest property value.
2010-03-07 21:51:14 +05:30
if new_revisions_all . filter ( id__gt = rev . id , prop = rev . prop , box_type = rev . box_type , page = rev . page ) . count ( ) > 0 :
UGLY_HACK = True
else :
d . append ( {
' page_id ' : rev . page . id ,
' prop ' : rev . prop ,
' old_val ' : rev . old_val ,
' new_val ' : rev . new_val ,
' box_type ' : rev . box_type ,
' box_id ' : rev . box_id ,
' uuid ' : rev . uuid ,
' rev_no ' : rev . id
} )
last_rev = self . current_revision ( )
return { ' revs ' : d , ' rev_id ' : last_rev }
def current_revision ( self ) :
try :
rev = Revision . objects . filter ( article = self ) . order_by ( ' -id ' ) [ 0 ]
return rev . id
except :
return 0
def editor_size ( self ) :
height = self . editor_width / self . aspect_ratio
return ( self . editor_width , height , )
def view_size ( self ) :
product = Product . objects . get ( pk = self . product . id )
aspect_ratio = product . typ . aspect_ratio
width = 800
height = int ( 800.0 / / aspect_ratio )
return ( width , height , )
def print_size ( self , print_width ) :
height = print_width / aspect_ratio
multiplier = print_width / ( self . editor_width + .0 )
return ( self . print_width , height , multiplier , )
def get_print_multiplier ( self , dpi ) :
product = Product . objects . get ( pk = self . product . id )
print_width_mm = product . typ . print_width
dpm = dpi / / 25.4
pixel_width = print_width_mm * dpm
m = pixel_width / / 800.0
return m
def __unicode__ ( self ) :
return self . name
def get_dict ( self , * args ) :
if len ( args ) > 0 :
m = args [ 0 ]
else :
m = 1
if len ( args ) > 1 :
page_id = args [ 1 ]
pages = Page . objects . filter ( id__iexact = page_id )
else :
pages = Page . objects . filter ( article = self ) . order_by ( ' page_no ' )
rList = [ ]
for p in pages :
rList . append ( p . get_dict ( m ) )
return rList
class Meta :
unique_together = ( ' product ' , ' order ' , )
class Page ( models . Model ) :
# Question: Does Page need some custom CSS definitions like bg_color, borders, etc. ?
page_no = models . IntegerField ( )
article = models . ForeignKey ( ' Article ' )
videos = models . ManyToManyField ( Video )
audios = models . ManyToManyField ( Audio )
background_color = models . CharField ( max_length = 30 )
def __unicode__ ( self ) :
return " %s : %s " % ( self . page_no , self . article )
def set_page_no ( self , new_page_no ) :
"""
Use this function to set a new page no . on a Page instance . Changes page numbers of other pages in article accordingly .
"""
old_page_no = self . page_no
self . page_no = new_page_no
self . save ( )
if new_page_no < old_page_no :
pages_after = Page . objects . filter ( article = self . article , page_no__gte = new_page_no ) . filter ( page_no__lt = old_page_no ) . exclude ( pk = self . id )
for a in pages_after :
a . page_no = a . page_no + 1
a . save ( )
else :
pages_before = Page . objects . filter ( article = self . article , page_no__lte = new_page_no ) . filter ( page_no__gt = old_page_no ) . exclude ( pk = self . id )
for b in pages_before :
b . page_no = b . page_no - 1
b . save ( )
return self
2010-05-14 04:08:22 +05:30
def deleteme ( self ) :
pages_after = Page . objects . filter ( article = self . article , page_no__gt = self . page_no )
for p in pages_after :
p . page_no = p . page_no - 1
p . save ( )
self . delete ( )
return
2010-03-07 21:51:14 +05:30
def get_dict ( self , m ) :
"""
This function iterates through all boxes on the page and returns a Dict representation which the view converts to json to send to front - end .
"""
# Image Boxes:
imageBoxes = [ ]
for i in ImageBox . objects . filter ( page = self ) . exclude ( is_displayed = False ) :
imageBoxes . append ( i . to_dict ( m ) )
# Text Boxes:
textBoxes = [ ]
for t in TextBox . objects . filter ( page = self ) . exclude ( is_displayed = False ) :
textBoxes . append ( t . to_dict ( ) )
'''
videos = [ ]
for v in self . videos :
r = {
' title ' = v . fil . title ,
' description ' = v . fil . description
' srts ' = [ ]
for s in v . srt :
'''
#Resources:
'''
resources = [ ]
for res in self . resources . all ( ) :
r = { }
r = {
' file ' : res . file ,
' title ' : res . title ,
' description ' : res . description ,
' tags ' : res . tags ,
' userID ' : res . userID ,
' added ' : res . added ,
' categories ' : res . categories
}
resources . append ( r )
'''
rDict = {
' id ' : self . id ,
' imageBoxes ' : imageBoxes ,
' textBoxes ' : textBoxes ,
# 'resources' : resources
}
return rDict
def save_from_dict ( self , d ) :
imageBoxes = d . imageBoxes
textBoxes = d . textBoxes
for i in imageBoxes :
img = ImageBox . objects . get ( pk = i . id )
img . save_from_dict ( )
for t in textBoxes :
txt = TextBox . objects . get ( pk = t . id )
txt . save_from_dict ( )
#The next two functions are painful. Whoever writes them wins a trip to the moon.
def save_revision ( self ) :
"""
This function saves the json for the page as a Page Revision .
"""
return True
def load_revision ( self , rev_no ) :
"""
Loads the json data from a revision to populate the page . First , of course , it saves its current state as a revision
"""
return True
def deleteme ( self ) :
"""
Deletes self - needs to change next page numbers .
"""
return True
class TextBox ( models . Model ) :
# Positioning stuff:
height = models . IntegerField ( )
width = models . IntegerField ( )
top = models . IntegerField ( )
left = models . IntegerField ( )
# Store reference to file containing text, if any:
file = models . ForeignKey ( File , blank = True , null = True )
# Content, reference to page and whether is_displayed:
html = models . TextField ( )
page = models . ForeignKey ( Page )
is_displayed = models . BooleanField ( default = True )
# CSS:
z_index = models . IntegerField ( )
background_color = models . CharField ( max_length = 16 )
border_style = models . CharField ( max_length = 16 )
border_width = models . IntegerField ( )
2010-06-29 18:34:33 +05:30
# direction = models.CharField(max_length=8)
2010-03-07 21:51:14 +05:30
# line_height = models.IntegerField(blank=True, null=True)
# letter_spacing = models.IntegerField(blank=True, null=True)
# word_spacing = models.IntegerField(blank=True, null=True)
border_color = models . CharField ( max_length = 10 )
border_radius = models . IntegerField ( )
padding_top = models . IntegerField ( )
padding_left = models . IntegerField ( )
padding_bottom = models . IntegerField ( )
padding_right = models . IntegerField ( )
opacity = models . FloatField ( )
def set_css ( self , prop , val ) :
p = prop . replace ( " - " , " _ " )
self . __setattr__ ( p , cleanCSS ( val ) )
return self
def get_css ( self , prop ) :
p = prop . replace ( " - " , " _ " )
r = self . __getattribute__ ( p )
return r
def to_dict ( self ) :
return {
' id ' : self . id ,
' html ' : self . html ,
' css ' : {
' height ' : addPx ( self . height ) ,
' width ' : addPx ( self . width ) ,
' top ' : addPx ( self . top ) ,
' left ' : addPx ( self . left ) ,
' z-index ' : self . z_index ,
' opacity ' : self . opacity ,
2010-06-29 18:12:01 +05:30
' direction ' : self . direction ,
2010-03-07 21:51:14 +05:30
' border-style ' : self . border_style ,
' border-width ' : addPx ( self . border_width ) ,
' border-color ' : self . border_color ,
' background-color ' : self . background_color ,
' border-radius ' : addPx ( self . border_radius ) ,
' padding-top ' : addPx ( self . padding_top ) ,
' padding-left ' : addPx ( self . padding_left ) ,
' padding-right ' : addPx ( self . padding_right ) ,
' padding-bottom ' : addPx ( self . padding_bottom )
}
}
def save_from_dict ( self , d ) :
self . html = d . html
self . save ( )
for prop in d . css :
self . set_css ( prop , d . css [ prop ] )
return self
class ImageBox ( models . Model ) :
# Positioning stuff:
height = models . IntegerField ( )
width = models . IntegerField ( )
top = models . IntegerField ( )
left = models . IntegerField ( )
# Data about the crop:
is_cropped = models . BooleanField ( default = False )
crop_x1 = models . IntegerField ( default = 0 )
crop_x2 = models . IntegerField ( default = 0 )
crop_y1 = models . IntegerField ( default = 0 )
crop_y2 = models . IntegerField ( default = 0 )
# Filename (originally uploaded), reference to page and whether is_displayed:
file = models . ForeignKey ( File )
page = models . ForeignKey ( Page )
is_displayed = models . BooleanField ( default = True )
# CSS:
z_index = models . IntegerField ( )
border_style = models . CharField ( max_length = 16 )
border_width = models . IntegerField ( )
border_color = models . CharField ( max_length = 10 )
opacity = models . FloatField ( )
def __unicode__ ( self ) :
return self . id
def set_css ( self , prop , val ) :
p = prop . replace ( " - " , " _ " )
self . __setattr__ ( p , cleanCSS ( val ) )
return self
def get_css ( self , prop ) :
p = prop . replace ( " - " , " _ " )
r = self . __getattribute__ ( p )
return r
def to_dict ( self , m ) :
return {
' id ' : self . id ,
' original_print ' : self . original_print ( ) ,
# 'original_web': i.original_web(),
' output_web ' : self . get_path ( m ) ,
' css ' : {
' height ' : addPx ( self . height ) ,
' width ' : addPx ( self . width ) ,
' top ' : addPx ( self . top ) ,
' left ' : addPx ( self . left ) ,
' z-index ' : int ( self . z_index ) ,
' opacity ' : self . opacity ,
' border-style ' : self . border_style ,
' border-width ' : addPx ( self . border_width ) ,
' border-color ' : self . border_color ,
} ,
#DIRTY HACK: Creates a 'resource' thingie to match the front-end droppable behaviour. Try n fix at some point.
' resource ' : {
' height ' : self . height * m ,
' width ' : self . width * m ,
' resized ' : self . get_path ( m )
}
}
def save_from_dict ( self , d ) :
self . html = d . html
for prop in d . css :
self . set_css ( prop , d . css [ prop ] )
return self
def from_dict ( self , d ) :
self . html = d . html
self . crop_top = d . crop_top
self . crop_bottom = d . crop_bottom
self . crop_left = d . crop_left
self . crop_right = d . crop_right
self . save ( )
for prop in d . css :
self . set_css ( prop , d . css [ prop ] )
return self
def resize ( self , width , height ) :
'''
self . width = width
self . height = height
self . save ( )
'''
return self
#Question: Can the following methods be constructed in such a way that if the filename (based on our naming conventions) exists, it returns the filename, else, it creates it ?
"""
This function returns the filename of the highest res original dimensions file , converted to jpeg
"""
def original_print ( self ) :
basePath = " media/images/original/ "
filename = os . path . basename ( str ( self . file . file ) )
if extFileName ( filename ) != ' jpg ' :
f = baseFileName ( filename ) + " .jpg "
else :
f = filename
return basePath + f
def crop ( self , x1 , y1 , x2 , y2 , width , height ) :
original_image = Image . open ( MEDIA_ROOT + " / " + self . actual_unresized ( ) )
original_width = original_image . size [ 0 ]
original_height = original_image . size [ 1 ]
current_width = self . width
current_height = self . height
divisor = original_width / ( current_width + .0 )
if self . is_cropped == False :
self . crop_x1 = int ( x1 * divisor )
self . crop_y1 = int ( y1 * divisor )
self . crop_x2 = int ( x2 * divisor )
self . crop_y2 = int ( y2 * divisor )
self . is_cropped = True
else :
old_x1 = self . crop_x1
old_y1 = self . crop_y1
old_x2 = self . crop_x2
old_y2 = self . crop_y2
self . crop_x1 = int ( x1 * divisor ) + old_x1
self . crop_y1 = int ( y1 * divisor ) + old_y1
self . crop_x2 = int ( x2 * divisor ) + old_x1
self . crop_y2 = int ( y2 * divisor ) + old_y1
self . width = width
self . height = height
self . save ( )
tpl = ( self . crop_x1 , self . crop_y1 , self . crop_x2 , self . crop_y2 , )
original_cropped = Image . open ( MEDIA_ROOT + " / " + self . original_print ( ) ) . crop ( tpl )
original_cropped . save ( self . cropped_path ( ) )
return self
def cropped_fname ( self ) :
filename = baseFileName ( os . path . basename ( self . original_print ( ) ) )
cropped_fname = " %s _ %d _ %d _ %d _ %d .jpg " % ( filename , self . crop_x1 , self . crop_y1 , self . crop_x2 , self . crop_y2 )
return cropped_fname
def cropped_path ( self ) :
return MEDIA_ROOT + " /media/images/cropped/ " + self . cropped_fname ( )
def actual_unresized ( self ) :
if self . is_cropped :
return " media/images/cropped/ " + self . cropped_fname ( )
else :
return self . original_print ( )
def get_path ( self , * args ) :
if len ( args ) > 0 :
m = args [ 0 ]
else :
m = 1
path = self . actual_unresized ( )
context = {
' path ' : path ,
' size ' : ( self . width * m , self . height * m , )
}
return get_template ( " thumbnailTmp.txt " ) . render ( Context ( context ) )
'''
def original_web ( self ) :
"""
This function returns the filename of the original dimensions file , converted to jpeg , low res for web
"""
return self
def output_print ( self ) :
"""
This function returns the filename of the high - res cropped file
"""
return self
def output_web ( self ) :
"""
This function returns the filename of the low - res cropped file
"""
return self
'''
class PageRev ( models . Model ) :
"""
Stores states of the page ( json representations of all boxes on page ) with revision numbers so user can revert to a previous state of the page
"""
page = models . ForeignKey ( Page )
pickle = models . TextField ( )
rev_no = models . IntegerField ( )
comment = models . TextField ( blank = True )
def __unicode__ ( self ) :
return " %s : %s " % ( self . page . page_no , self . rev_no )
class SliderImage ( models . Model ) :
small_image = models . ImageField ( upload_to = ' images/small/ ' , width_field = ' width ' , height_field = ' height ' , blank = False , null = False )
big_image = models . ImageField ( upload_to = ' images/big/ ' , width_field = ' width ' , height_field = ' height ' , blank = False , null = False )
width = models . IntegerField ( editable = False )
height = models . IntegerField ( editable = False )
caption = models . CharField ( max_length = 255 , blank = True )
def __unicode__ ( self ) :
return self . caption
def comments_notify ( sender , * * kwargs ) :
comment = kwargs [ ' comment ' ]
name = comment . name
email = comment . email
content = comment . comment
img_id = comment . content_object . id
url = " http://edgwareroad.org/slider/ %d " % ( img_id )
message = " Page: %s \n Name: %s \n Email: %s \n Comment: %s " % ( url , name , email , content )
send_mail ( " New comment on edgwareroad.org " , message , " do_not_reply@edgwareroad.org " , [ " hello@edgwareroad.org " ] )
# f = open("/home/sanj/tmp/edgeTest.txt", "w")
# f.write(message)
return True
comment_was_posted . connect ( comments_notify )