class ArticleForm(forms.ModelForm):

    summary = forms.CharField(widget=forms.Textarea)

    comment = forms.CharField(required=False)
    user_ip = forms.CharField(widget=forms.HiddenInput)

    content_type = forms.ModelChoiceField(
        queryset=ContentType.objects.all(),
        required=False,
        widget=forms.HiddenInput)
    object_id = forms.IntegerField(required=False,
                                   widget=forms.HiddenInput)

    action = forms.CharField(widget=forms.HiddenInput)

    class Meta:
        model = Article
        exclude = ('creator', 'creator_ip', 'removed',
                   'group', 'created_at', 'last_update')

    def clean_title(self):
        """ Page title must be a WikiWord.
        """
        title = self.cleaned_data['title']
        if not wikiword_pattern.match(title):
            raise forms.ValidationError(_('Must be a WikiWord.'))

        return title

    def clean(self):
        super(ArticleForm, self).clean()
        kw = {}

        if self.cleaned_data['action'] == 'create':
            try:
                kw['title'] = self.cleaned_data['title']
                kw['content_type'] = self.cleaned_data['content_type']
                kw['object_id'] = self.cleaned_data['object_id']
            except KeyError:
                pass # some error in this fields
            else:
                if Article.objects.filter(**kw).count():
                    raise forms.ValidationError(
                        _("An article with this title already exists."))

        return self.cleaned_data

    def save(self):
        # 0 - Extra data
        editor_ip = self.cleaned_data['user_ip']
        comment = self.cleaned_data['comment']

        # 1 - Get the old stuff before saving
        if self.instance.id is None:
            old_title = old_content = old_markup = ''
            new = True
        else:
            old_title = self.instance.title
            old_content = self.instance.content
            old_markup = self.instance.markup
            new = False

        # 2 - Save the Article
        article = super(ArticleForm, self).save()

        # 3 - Set creator and group
        editor = getattr(self, 'editor', None)
        group = getattr(self, 'group', None)
        if new:
            article.creator_ip = editor_ip
            if editor is not None:
                article.creator = editor
                article.group = group
            article.save()

        # 4 - Create new revision
        changeset = article.new_revision(
            old_content, old_title, old_markup,
            comment, editor_ip, editor)

        return article, changeset class SearchForm(forms.Form):
    search_term = forms.CharField(required=True) class Article(models.Model):
    """ A wiki page.
    """
    title = models.CharField(_(u"Title"), max_length=50)
    content = models.TextField(_(u"Content"))
    summary = models.CharField(_(u"Summary"), max_length=150,
                               null=True, blank=True)
    markup = models.CharField(_(u"Content Markup"), max_length=3,
                              choices=markup_choices,
                              null=True, blank=True)
    creator = models.ForeignKey(User, verbose_name=_('Article Creator'),
                                null=True)
    creator_ip = models.IPAddressField(_("IP Address of the Article Creator"),
                                       blank=True, null=True)
    created_at = models.DateTimeField(default=datetime.now)
    last_update = models.DateTimeField(blank=True, null=True)
    removed = models.BooleanField(_("Is removed?"), default=False)

    content_type = models.ForeignKey(ContentType, null=True)
    object_id = models.PositiveIntegerField(null=True)
    group = generic.GenericForeignKey('content_type', 'object_id')

    tags = TagField()

    objects = QuerySetManager()

    non_removed_objects = NonRemovedArticleManager()

    class QuerySet(QuerySet):

        def get_by(self, title, group=None):
            if group is None:
                return self.get(title=title)
            return group.get_related_objects(self.filter(title=title)).get()
            
    class Meta:
        verbose_name = _(u'Article')
        verbose_name_plural = _(u'Articles')

    def get_absolute_url(self):
        if self.group is None:
            return reverse('wiki_article', args=(self.title,))
        return self.group.get_absolute_url() + 'wiki/' + self.title

    def save(self, force_insert=False, force_update=False):
        self.last_update = datetime.now()
        super(Article, self).save(force_insert, force_update)

    def remove(self):
        """ Mark the Article as 'removed'. If the article is
        already removed, delete it.
        Returns True if the article was deleted, False when just marked
        as removed.
        """
        if self.removed:
            self.delete()
            return True
        else:
            self.removed = True
            self.save()
            return False

    def latest_changeset(self):
        try:
            return self.changeset_set.filter(
                reverted=False).order_by('-revision')[0]
        except IndexError:
            return ChangeSet.objects.none()

    def new_revision(self, old_content, old_title, old_markup,
                     comment, editor_ip, editor):
        '''Create a new ChangeSet with the old content.'''

        content_diff = diff(self.content, old_content)

        cs = ChangeSet.objects.create(
            article=self,
            comment=comment,
            editor_ip=editor_ip,
            editor=editor,
            old_title=old_title,
            old_markup=old_markup,
            content_diff=content_diff)

        if None not in (notification, self.creator):
            if editor is None:
                editor = editor_ip
            notification.send([self.creator], "wiki_article_edited",
                              {'article': self, 'user': editor})

        return cs

    def revert_to(self, revision, editor_ip, editor=None):
        """ Revert the article to a previuos state, by revision number.
        """
        changeset = self.changeset_set.get(revision=revision)
        changeset.reapply(editor_ip, editor)


    def __unicode__(self):
        return self.title class ChangeSet(models.Model):
    """A report of an older version of some Article."""

    article = models.ForeignKey(Article, verbose_name=_(u"Article"))

    # Editor identification -- logged or anonymous
    editor = models.ForeignKey(User, verbose_name=_(u'Editor'),
                               null=True)
    editor_ip = models.IPAddressField(_(u"IP Address of the Editor"))

    # Revision number, starting from 1
    revision = models.IntegerField(_(u"Revision Number"))

    # How to recreate this version
    old_title = models.CharField(_(u"Old Title"), max_length=50, blank=True)
    old_markup = models.CharField(_(u"Article Content Markup"), max_length=3,
                                  choices=markup_choices,
                                  null=True, blank=True)
    content_diff = models.TextField(_(u"Content Patch"), blank=True)

    comment = models.CharField(_(u"Editor comment"), max_length=50, blank=True)
    modified = models.DateTimeField(_(u"Modified at"), default=datetime.now)
    reverted = models.BooleanField(_(u"Reverted Revision"), default=False)

    objects = QuerySetManager()
    non_reverted_objects = NonRevertedChangeSetManager()

    class QuerySet(QuerySet):
        def all_later(self, revision):
            """ Return all changes later to the given revision.
            Util when we want to revert to the given revision.
            """
            return self.filter(revision__gt=int(revision))


    class Meta:
        verbose_name = _(u'Change set')
        verbose_name_plural = _(u'Change sets')
        get_latest_by  = 'modified'
        ordering = ('-revision',)

    def __unicode__(self):
        return u'#%s' % self.revision

    @models.permalink
    def get_absolute_url(self):
        if self.article.group is None:
            return ('wiki_changeset', (),
                    {'title': self.article.title,
                     'revision': self.revision})
        return ('wiki_changeset', (),
                {'group_slug': self.article.group.slug,
                 'title': self.article.title,
                 'revision': self.revision})


    def is_anonymous_change(self):
        return self.editor is None

    def reapply(self, editor_ip, editor):
        """ Return the Article to this revision.
        """

        # XXX Would be better to exclude reverted revisions
        #     and revisions previous/next to reverted ones
        next_changes = self.article.changeset_set.filter(
            revision__gt=self.revision).order_by('-revision')

        article = self.article

        content = None
        for changeset in next_changes:
            if content is None:
                content = article.content
            patch = dmp.patch_fromText(changeset.content_diff)
            content = dmp.patch_apply(patch, content)[0]

            changeset.reverted = True
            changeset.save()

        old_content = article.content
        old_title = article.title
        old_markup = article.markup

        article.content = content
        article.title = changeset.old_title
        article.markup = changeset.old_markup
        article.save()

        article.new_revision(
            old_content=old_content, old_title=old_title,
            old_markup=old_markup,
            comment="Reverted to revision #%s" % self.revision,
            editor_ip=editor_ip, editor=editor)

        self.save()

        if None not in (notification, self.editor):
            notification.send([self.editor], "wiki_revision_reverted",
                              {'revision': self, 'article': self.article})

    def save(self, force_insert=False, force_update=False):
        """ Saves the article with a new revision.
        """
        if self.id is None:
            try:
                self.revision = ChangeSet.objects.filter(
                    article=self.article).latest().revision + 1
            except self.DoesNotExist:
                self.revision = 1
        super(ChangeSet, self).save(force_insert, force_update)

    def display_diff(self):
        ''' Returns a HTML representation of the diff.
        '''

        # well, it *will* be the old content
        old_content = self.article.content

        # newer non-reverted revisions of this article, starting from this
        newer_changesets = ChangeSet.non_reverted_objects.filter(
            article=self.article,
            revision__gte=self.revision)

        # apply all patches to get the content of this revision
        for i, changeset in enumerate(newer_changesets):
            patches = dmp.patch_fromText(changeset.content_diff)
            if len(newer_changesets) == i+1:
                # we need to compare with the next revision after the change
                next_rev_content = old_content
            old_content = dmp.patch_apply(patches, old_content)[0]

        diffs = dmp.diff_main(old_content, next_rev_content)
        return dmp.diff_prettyHtml(diffs) class RssArticleHistoryFeed(Feed):

    def __init__(self, title, request, 
                group_slug=None, bridge=None,
                article_qs=ALL_ARTICLES, changes_qs=ALL_CHANGES,
                extra_context=None,
                title_template = u'feeds/history_title.html',
                description_template = u'feeds/history_description.html',
                *args, **kw):

        if  group_slug is not None:
            try:
                group = bridge.get_group(group_slug)
            except ObjectDoesNotExist:
                raise Http404
            self.article_qs = article_qs.filter(content_type=get_ct(group),
                                           object_id=group.id)
        else:
            self.article_qs = article_qs

        self.title_template = title_template
        self.description_template = description_template
        super(RssArticleHistoryFeed, self).__init__(title, request)

    def get_object(self, bits):
        return self.article_qs.get(title = bits[0])

    def title(self, obj):
        return "History for: %s " % obj.title

    def link(self, obj):
        if not obj:
            raise FeedDoesNotExist
        return obj.get_absolute_url()

    def description(self, obj):
        return "Recent changes in %s" % obj.title

    def items(self, obj):
        return ChangeSet.objects.filter(article__id__exact=obj.id).order_by('-modified')[:30]

    def item_pubdate(self, item):
        """
        Returns the modified date
        """
        return item.modified class AtomArticleHistoryFeed(atom.Feed):
    
    def __init__(self, title, request, 
                group_slug=None, bridge=None,
                article_qs=ALL_ARTICLES, changes_qs=ALL_CHANGES,
                extra_context=None,
                title_template = u'feeds/history_title.html',
                description_template = u'feeds/history_description.html',
                *args, **kw):

        if  group_slug is not None:
            try:
                group = bridge.get_group(group_slug)
            except ObjectDoesNotExist:
                raise Http404
            self.article_qs = article_qs.filter(content_type=get_ct(group),
                                           object_id=group.id)
        else:
            self.article_qs = article_qs

        self.title_template = get_template(title_template)
        self.description_template = get_template(description_template)
        super(AtomArticleHistoryFeed, self).__init__('', request)

    def get_object(self, bits):
        return self.article_qs.get(title = bits[0])

    def feed_title(self, obj):
        return "History for: %s " % obj.title

    def feed_subtitle(self, obj):
        return "Recent changes in %s" % obj.title

    def feed_id(self):
        return "feed_id"

    def items(self, obj):
        return ChangeSet.objects.filter(article__id__exact=obj.id).order_by('-modified')[:30]

    def item_id(self, item):
        return "%s" % item.id

    def item_title(self, item):
        c = Context({'obj' : item})
        return self.title_template.render(c)

    def item_updated(self, item):
        return item.modified

    def item_authors(self, item):
        if item.is_anonymous_change():
            return [{'name' : _('Anonimous')},]
        return [{'name' : item.editor.username},]

    def item_links(self, item):
        return [{'href': item.get_absolute_url()},]

    def item_content(self, item):
        c = Context({'obj' : item, })
        return ({'type': 'html'}, self.description_template.render(c)) class RssHistoryFeed(Feed):

    title = 'History for all articles'
    link = '/wiki/'
    description = 'Recent changes in wiki'

    def __init__(self, request,
                 group_slug=None, bridge=None,
                 article_qs=ALL_ARTICLES, changes_qs=ALL_CHANGES, 
                 extra_context=None, 
                 title_template = u'feeds/history_title.html', 
                 description_template = u'feeds/history_description.html', 
                 *args, **kw):

        if  group_slug is not None:
            try:
                group = bridge.get_group(group_slug)
            except ObjectDoesNotExist:
                raise Http404
            self.changes_qs = changes_qs.filter(article__content_type=get_ct(group), 
                                                article__object_id=group.id)
        else:
            self.changes_qs = changes_qs

        self.title_template = title_template
        self.description_template = description_template
        super(RssHistoryFeed, self).__init__('', request)

    def items(self):
        return self.changes_qs.order_by('-modified')[:30]
        
    def item_pubdate(self, item):
        """
        Return the item's pubdate. It's this modified date
        """
        return item.modified class AtomHistoryFeed(atom.Feed):

    feed_title = 'History for all articles'
    feed_subtitle = 'Recent changes in wiki'

    def __init__(self, request,
                 group_slug=None, bridge=None, 
                 article_qs=ALL_ARTICLES, changes_qs=ALL_CHANGES, 
                 extra_context=None, 
                 title_template = u'feeds/history_title.html', 
                 description_template = u'feeds/history_description.html', 
                 *args, **kw):

        if  group_slug is not None:
            try:
                group = bridge.get_group(group_slug)
            except ObjectDoesNotExist:
                raise Http404
            self.changes_qs = changes_qs.filter(article__content_type=get_ct(group), 
                                                article__object_id=group.id)
        else:
            self.changes_qs = changes_qs

        self.title_template = get_template(title_template)
        self.description_template = get_template(description_template)
        super(AtomHistoryFeed, self).__init__('', request)

    def feed_id(self):
        return "feed_id"

    def items(self):
        return self.changes_qs.order_by('-modified')[:30]

    def item_id(self, item):
        return "%s" % item.id

    def item_title(self, item):
        c = Context({'obj' : item})
        return self.title_template.render(c)

    def item_updated(self, item):
        return item.modified

    def item_authors(self, item):
        if item.is_anonymous_change():
            return [{'name' : _('Anonimous')},]
        return [{'name' : item.editor.username},]

    def item_links(self, item):
        return [{'href': item.get_absolute_url()}, ]

    def item_content(self, item):
        c = Context({'obj' : item,})
        return ({'type': 'html'}, self.description_template.render(c)) def get_ct(obj):
    """ Return the ContentType of the object's model.
    """
    return ContentType.objects.get(app_label=obj._meta.app_label,
                                   model=obj._meta.module_name) def login_required(function):
    if getattr(settings, 'WIKI_REQUIRES_LOGIN', False):
        return _login_required(function)
    return function
import os
from datetime import datetime
from django.conf import settings
from django.core.cache import cache
from django.template import RequestContext
from django.core.urlresolvers import reverse
from django.core.exceptions import ObjectDoesNotExist
from django.http import (Http404, HttpResponseRedirect,
                         HttpResponseNotAllowed, HttpResponse, HttpResponseForbidden)
from django.shortcuts import get_object_or_404, render_to_response
from django.views.generic.simple import redirect_to
from django.utils.translation import ugettext_lazy as _
from django.contrib.contenttypes.models import ContentType
from django.contrib.syndication.feeds import FeedDoesNotExist
from wiki.forms import ArticleForm, SearchForm
from wiki.models import Article, ChangeSet
from wiki.feeds import (RssArticleHistoryFeed, AtomArticleHistoryFeed,
                        RssHistoryFeed, AtomHistoryFeed)
from wiki.utils import get_ct, login_required
    from notification import models as notification
# -*- coding: utf-8 -*-





# Settings

#  lock duration in minutes
try:
    WIKI_LOCK_DURATION = settings.WIKI_LOCK_DURATION
except AttributeError:
    WIKI_LOCK_DURATION = 15

try:
except ImportError:
    notification = None

# default querysets

ALL_ARTICLES = Article.non_removed_objects.all()