import datetime, copy, xmlrpclib, thread, time, cgi
from django.db import models
from django.core.cache import cache
from django.contrib.sitemaps import ping_google
from django.contrib.sites.models import Site
from django.dispatch import dispatcher
from django.db.models import signals
from django.core.mail import mail_admins
from lifeflow.markdown.markdown import Markdown
def dbc_markup(txt, obj=None):
"Apply Dynamic Blog Context markup"
from lifeflow.markdown import mdx_lifeflow
from lifeflow.markdown import mdx_code
from lifeflow.markdown import mdx_footnotes
md = Markdown(txt,
extensions=[mdx_footnotes,
mdx_code,
mdx_lifeflow],
extension_configs={'lifeflow':obj},
)
return md.convert()
def comment_markup(txt, obj=None):
from lifeflow.markdown import mdx_code
md = Markdown(txt, extensions=[mdx_code])
return md.convert()
class Author(models.Model):
name = models.CharField(max_length=100)
slug = models.SlugField(
prepopulate_from=('name',),
help_text="Automatically built from author's name.",
)
link = models.CharField(
max_length=200,
help_text="Link to author's website.")
bio = models.TextField(
blank=True, null=True,
help_text="Bio of author, written in markdown format."
)
picture = models.FileField(
upload_to="lifeflow/author", blank=True, null=True,
help_text="Picture of author. For best visual appearance should be relatively small (200px by 200px or so)."
)
use_markdown = models.BooleanField(
default=True,
help_text="If true body is filtered using MarkDown, otherwise html is expected.",
)
class Meta:
ordering = ('name',)
class Admin:
list_display = ('name', 'link')
search_fields = ['name']
def __unicode__(self):
return self.name
def get_absolute_url(self):
return u"/author/%s/" % self.slug
def latest(self, qty=10):
return self.entry_set.all().filter(**{'pub_date__lte': datetime.datetime.now()})[:qty]
def name_with_link(self):
return u'%s' % (self.get_absolute_url(), self.name)
class Comment(models.Model):
entry = models.ForeignKey('Entry')
parent = models.ForeignKey('Comment', blank=True, null=True)
name = models.CharField(max_length=100, blank=True, null=True)
email = models.CharField(max_length=100, blank=True, null=True)
webpage = models.CharField(max_length=100, blank=True, null=True)
body = models.TextField()
date = models.DateTimeField(auto_now_add=True)
html = models.TextField(blank=True, null=True)
class Meta:
ordering = ('-date',)
class Admin:
list_display = ('entry', 'name', 'email', 'webpage', 'date')
search_fields = ['name', 'email','body']
def save(self):
if self.name == u"name" or self.name == u"":
self.name = u"anonymous"
if self.webpage == u"http://webpage" or self.webpage == u"http://":
# better to check for valid URL
self.webpage = None
if self.email == u"email":
# better to check for valid email address
self.email = None
title = self.entry.title
subject = u"[Comment] %s on %s" % (self.name, self.entry.title)
body = u"Comment by %s [%s][%s] on %s\n\n%s" % (self.name, self.email, self.webpage, title, self.html)
mail_admins(subject, body, fail_silently=True)
super(Comment,self).save()
def get_absolute_url(self):
return u"%s#comment_%s" % (self.entry.get_absolute_url(), self.pk)
def __unicode__(self):
name = self.name or "Unnamed Poster"
title = self.entry.title or "Unnamed Entry"
return u": ".join((name, title))
class Draft(models.Model):
title = models.CharField(max_length=200, blank=True, null=True)
slug = models.SlugField(unique_for_date='pub_date',
prepopulate_from=('title',),
blank=True, null=True)
summary = models.TextField(blank=True, null=True)
body = models.TextField(blank=True, null=True)
pub_date = models.DateTimeField(blank=True, null=True)
edited = models.BooleanField(default=False)
use_markdown = models.BooleanField(default=True)
is_translation = models.BooleanField(default=False)
send_ping = models.BooleanField(default=False)
allow_comments = models.BooleanField(default=True)
flows = models.ManyToManyField('Flow', blank=True, null=True)
tags = models.ManyToManyField('Tag', blank=True, null=True)
series = models.ManyToManyField('Series', blank=True, null=True)
authors = models.ManyToManyField('Author', blank=True, null=True)
def __unicode__(self):
if self.title:
return self.title
else:
return "Untitled Draft"
class CurrentEntryManager(models.Manager):
def get_query_set(self):
return super(CurrentEntryManager, self).get_query_set().filter(**{'pub_date__lte': datetime.datetime.now()}).filter(**{'is_translation':False})
class Entry(models.Model):
title = models.CharField(
max_length=200,
help_text='Name of this entry.'
)
slug = models.SlugField(
unique_for_date='pub_date',
prepopulate_from=('title',),
help_text='Automatically built from the title.'
)
summary = models.TextField(help_text="One paragraph. Don't add <p> tag.")
body = models.TextField(
help_text='Use Markdown-syntax'
)
body_html = models.TextField(blank=True, null=True)
pub_date = models.DateTimeField(
help_text='If the date and time combination is in the future, the entry will not be visible until after that moment has passed.'
)
use_markdown = models.BooleanField(
default=True,
help_text="If true body is filtered using MarkDown++, otherwise no filtering is applied.",
)
is_translation = models.BooleanField(
default=False,
help_text="Only used to add articles to the translation feed.",
)
send_ping = models.BooleanField(
default=False,
help_text="If true will ping Google and any sites you have specified on saves."
)
allow_comments = models.BooleanField(
default=True,
help_text="If true users may add comments on this entry.",
)
flows = models.ManyToManyField(
'Flow', filter_interface=models.HORIZONTAL, blank=True, null=True,
help_text="Determine which pages and feeds to show entry on.",
)
tags = models.ManyToManyField(
'Tag', filter_interface=models.HORIZONTAL, blank=True, null=True,
help_text="Select tags to associate with this entry.",
)
series = models.ManyToManyField(
'Series', filter_interface=models.HORIZONTAL, blank=True, null=True,
help_text='Used to associated groups of entries together under one theme.',
)
resources = models.ManyToManyField(
'Resource', filter_interface=models.HORIZONTAL, blank=True, null=True,
help_text='Files or images used in entries. MarkDown links are automatically generated.',
)
authors = models.ManyToManyField(
'Author', filter_interface=models.HORIZONTAL, blank=True, null=True,
help_text='The authors associated with this entry.',
)
# main manager, allows access to all entries, required primarily for admin functionality
objects = models.Manager()
# current manager, does not allow access entries published to future dates
current = CurrentEntryManager()
class Meta:
ordering = ('-pub_date',)
get_latest_by = 'pub_date'
verbose_name_plural = "entries"
class Admin:
list_display = ('title', 'pub_date')
search_fields = ['title', 'summary', 'body']
fields = (
(None, {'fields' : ('title', 'slug', 'pub_date',)}),
('Content', {'fields': ('summary', 'body',)}),
('Options', {'fields': ('use_markdown', 'is_translation', 'send_ping', 'allow_comments', ), 'classes': 'collapse'}),
('Authors', {'fields' : ('authors',), 'classes': 'collapse'}),
('Resources', {'fields' : ('resources',), 'classes': 'collapse'}),
('Series', {'fields': ('series',), 'classes': 'collapse'}),
('Organization', {'fields': ('flows', 'tags',),}),
)
def __unicode__(self):
return self.title
def get_absolute_url(self):
return u"/entry/%s/%s/" % (
self.pub_date.strftime("%Y/%b/%d").lower(),
self.slug,
)
def save(self):
if self.use_markdown:
self.body_html = dbc_markup(self.body, self)
else:
self.body_html = self.body
if self.send_ping is True: self.ping()
super(Entry,self).save()
def ping(self):
# ping all sites to ping (Ping-O-Matic, etc)
for site in SiteToNotify.objects.all():
site.ping()
# inform Google sitemap has changed
try:
ping_google()
except Exception:
pass
def get_next_article(self):
next = Entry.current.filter(**{'pub_date__gt': self.pub_date}).order_by('pub_date')
try:
return next[0]
except IndexError:
return None
def get_previous_article(self):
previous = Entry.current.filter(**{'pub_date__lt': self.pub_date}).order_by('-pub_date')
try:
return previous[0]
except IndexError:
return None
def get_random_entries(self):
return Entry.current.order_by('?')[:3]
def organize_comments(self):
"""
Used to create a list of threaded comments.
This is a bit tricky since we only know the parent for
each comment, as opposed to knowing each parent's children.
"""
def build_relations(dict, comment=None, depth=-1):
if comment is None: id = None
else: id = comment.id
try:
children = dict[id]
return [(comment, depth), [build_relations(dict, x, depth+1) for x in children]]
except:
return (comment, depth)
def flatten(l, ltypes=(list, tuple)):
i = 0
while i < len(l):
while isinstance(l[i], ltypes):
if not l[i]:
l.pop(i)
if not len(l):
break
else:
l[i:i+1] = list(l[i])
i += 1
return l
def group(seq, length):
"""
Taken from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/496784
"""
return [seq[i:i+length] for i in range(0, len(seq), length)]
dict = {None:[]}
all = Comment.objects.select_related().filter(entry=self)
for comment in all:
if comment.parent: id = comment.parent.id
else: id = None
try:
dict[id].append(comment)
except KeyError:
dict[id] = [comment]
relations = build_relations(dict)
# If there are no comments, return None
if len(relations) == 1:
return None
# Otherwise, throw away the None node, flatten
# the returned list, and regroup the list into
# 2-lists that look like
# [CommentInstance, 4]
# where CommentInstance is an instance of the
# Comment class, and 4 is the depth of the
# comment in the layering
else:
return group(flatten(relations[1]), 2)
class Flow(models.Model):
"""
A genre of entries. Like things about Cooking, or Japan.
Broader than a tag, and gets its own nav link and is available
at /slug/ instead of /tags/slug/
"""
title = models.CharField(max_length=100)
slug = models.SlugField(prepopulate_from=("title",))
class Admin:
pass
def __unicode__(self):
return self.title
def latest(self, qty=None):
if qty is None:
return self.entry_set.all().filter(**{'pub_date__lte': datetime.datetime.now()}).filter(**{'is_translation':False})
else:
return self.entry_set.all().filter(**{'pub_date__lte': datetime.datetime.now()}).filter(**{'is_translation':False})[:qty]
def get_absolute_url(self):
return u"/%s/" % self.slug
class Language(models.Model):
title = models.CharField(max_length=50, core=True)
slug = models.SlugField(prepopulate_from=("title",))
class Admin:
pass
def __unicode__(self):
return self.title
def get_absolute_url(self):
return u"/language/%s/" % self.slug
def latest(self, qty=None):
return self.translation_set.all().filter(**{'translated__pub_date__lte': datetime.datetime.now()})
class Project(models.Model):
"""
A project of any kind. Think of it as a piece in a portfolio.
"""
title = models.CharField(max_length=50)
slug = models.SlugField(
prepopulate_from=('title',),
help_text='Automatically built from the title.'
)
summary = models.TextField(help_text="One paragraph. Don't add <p> tag.")
body = models.TextField(
help_text='Use Markdown-syntax')
body_html = models.TextField(blank=True, null=True)
use_markdown = models.BooleanField(default=True)
language = models.CharField(
max_length=50,
help_text="The programming language the project is written in.",
)
license = models.CharField(
max_length=50,
help_text="The license under which the project is released.",
)
resources = models.ManyToManyField('Resource', filter_interface=models.HORIZONTAL, blank=True, null=True)
SIZE_CHOICES = (
('0', 'Script'),
('1', 'Small'),
('2', 'Medium'),
('3', 'Large'),
)
size = models.CharField(
max_length=1, choices=SIZE_CHOICES,
help_text="Used for deciding order projects will be displayed in.",
)
class Meta:
ordering = ('-size',)
class Admin:
list_display = ('title', 'language', 'license', 'size',)
search_fields = ['title', 'summary', 'body']
fields = (
(None, {
'fields' : ('title', 'slug', 'size', 'language', 'license', 'use_markdown',)}),
('Content', {'fields': ('summary', 'body', 'resources')}),
)
def __unicode__(self):
return self.title
def size_string(self):
if self.size == str(0): return "Script"
if self.size == str(1): return "Small"
elif self.size == str(2): return "Medium"
elif self.size == str(3): return "Large"
def get_absolute_url(self):
return u"/projects/%s/" % self.slug
def save(self):
if self.use_markdown:
self.body_html = dbc_markup(self.body, self)
else:
self.body_html = self.body
super(Project,self).save()
class Resource(models.Model):
"""
A wrapper for files (image or otherwise, the model is unaware of the
distinction) that are used in blog entries.
"""
title = models.CharField(max_length=50)
markdown_id = models.CharField(max_length=50)
content = models.FileField(upload_to="lifeflow/resource")
class Admin:
pass
def get_relative_url(self):
# figure out why I named this relative instead of absolute
# because... it sure as hell isn't relative
return u"/media/%s" % self.content
def __unicode__(self):
return u"[%s] %s" % (self.markdown_id, self.title,)
class RecommendedSite(models.Model):
"""
A site that is displayed under the 'Blogs-To-See' entry
on each page of the website. Akin to entries in a blog roll
on a WordPress blog.
"""
title = models.CharField(max_length=50)
url = models.URLField()
class Admin:
pass
def __unicode__(self):
return u"%s ==> %s" % (self.title, self.url)
class Series(models.Model):
"""
A series is a collection of Entry instances on the same theme.
"""
title = models.CharField(max_length=200, core=True)
slug= models.SlugField(prepopulate_from=("title",))
class Meta:
verbose_name_plural = "Series"
class Admin:
pass
def __unicode__(self):
return self.title
def get_absolute_url(self):
return u"/articles/%s/" % ( unicode(self.slug), )
def latest(self, qty=10):
return self.entry_set.all().filter(**{'pub_date__lte': datetime.datetime.now()})[:qty]
def num_articles(self):
return self.entry_set.all().count()
class SiteToNotify(models.Model):
"""
SiteToNotify instances are pinged by Entries where
someEntry.ping_sites is True.
Sites such as 'Ping-O-Matic' are easiest to use here.
Manually creating the Ping-O-Matic instance looks
something like this:
stn = SiteToNotify(title="Ping-O-Matic",
url_to_ping="http://rpc.pingomatic.com/",
blog_title="My Blog's Title",
blog_url="http://www.myblog.com")
stn.save()
"""
title = models.CharField(max_length=100)
url_to_ping = models.CharField(max_length=200)
blog_title = models.CharField(max_length=100)
blog_url = models.CharField(max_length=200)
class Meta:
verbose_name_plural = "Sites to Notify"
class Admin:
pass
def __unicode__(self):
return self.title
def ping(self):
def do_ping():
remote_server = xmlrpclib.Server(self.url_to_ping)
remote_server.weblogUpdates.ping(self.blog_title, self.blog_url)
thread.start_new_thread(do_ping, ())
class Tag(models.Model):
"Tags are associated with Entry instances to describe their contents."
title = models.CharField(max_length=50, core=True)
slug = models.SlugField(prepopulate_from=("title",))
class Admin:
pass
class Meta:
ordering = ('title',)
def __unicode__(self):
return self.title
def get_absolute_url(self):
return u"/tags/%s/" % self.slug
def latest(self, qty=None):
if qty is None:
return self.entry_set.all().filter(**{'pub_date__lte': datetime.datetime.now()})
else:
return self.entry_set.all().filter(**{'pub_date__lte': datetime.datetime.now()})[:qty]
def get_max_tags(self):
max = cache.get('lifeflow_tags_max')
if max == None:
tags = Tag.objects.all()
max = 0
for tag in tags._get_data():
count = tag.entry_set.count()
if count > max: max = count
cache.set('lifeflow_tags_max', max)
return max
def tag_size(self):
max = self.get_max_tags()
count = self.entry_set.count()
ratio = (count * 1.0) / max
tag_name = "size"
if ratio < .2: return tag_name + "1"
elif ratio < .4: return tag_name + "2"
elif ratio < .6: return tag_name + "3"
elif ratio < .8: return tag_name + "4"
else: return tag_name + "5"
class Translation(models.Model):
"""
Link together two entries, where @translated is a translation of
@original in the language @language.
"""
language = models.ForeignKey('Language')
original = models.ForeignKey('Entry')
translated = models.ForeignKey('Entry', related_name="translated")
class Admin:
pass
def __unicode__(self):
return u"Translation of %s into %s" % (self.original, self.language,)
def get_link(self):
url = self.translated.get_absolute_url()
return u'%s' % (url, self.language,)
def get_absolute_url(self):
return self.translated.get_absolute_url()
def resave_object(sender, instance, signal, *args, **kwargs):
"""
This is called to save objects a second time after required
manyTomany relationships have been established.
There must be a better way of handling this.
"""
def do_save():
time.sleep(3)
try:
instance.save()
except:
pass
id = u"%s%s" % (unicode(instance), unicode(instance.id))
try:
should_resave = resave_hist[id]
except KeyError:
resave_hist[id] = True
should_resave = True
if should_resave is True:
resave_hist[id] = False
thread.start_new_thread(do_save, ())
else:
resave_hist[id] = True
resave_hist = {}
dispatcher.connect(resave_object, signal=signals.post_save, sender=Project)
dispatcher.connect(resave_object, signal=signals.post_save, sender=Entry)