diff --git a/chitatel/settings.py b/chitatel/settings.py index f1dbc6f..03921cb 100644 --- a/chitatel/settings.py +++ b/chitatel/settings.py @@ -134,6 +134,11 @@ 'django.contrib.staticfiles.finders.AppDirectoriesFinder', ) +# File upload settings +FILE_UPLOAD_HANDLERS = ( + "django.core.files.uploadhandler.TemporaryFileUploadHandler", +) + # Other settings ALLOWED_HOSTS = ['127.0.0.1'] INTERNAL_IPS = ['127.0.0.1'] diff --git a/chitatel/urls.py b/chitatel/urls.py index 21d7c26..246f38f 100644 --- a/chitatel/urls.py +++ b/chitatel/urls.py @@ -17,8 +17,8 @@ urlpatterns = patterns( '', - url(r'^$', include('feeds.urls')), - url(r'^$', include('users.urls')), + url(r'^feeds/', include('feeds.urls')), + url(r'^users/', include('users.urls')), url(r'^admin/doc/', include('django.contrib.admindocs.urls')), url(r'^admin/', include(admin.site.urls)), diff --git a/feeds/models.py b/feeds/models.py index b5e72e7..4bdc331 100644 --- a/feeds/models.py +++ b/feeds/models.py @@ -35,6 +35,12 @@ class Meta: verbose_name = _('feed') verbose_name_plural = _('feeds') + def __unicode__(self): + """ + Unicode representation. + """ + return self.title + class Entry(models.Model): """ diff --git a/feeds/urls.py b/feeds/urls.py index 56217c5..f8be924 100644 --- a/feeds/urls.py +++ b/feeds/urls.py @@ -7,9 +7,11 @@ """ -from django.conf.urls import patterns +from django.conf.urls import patterns, url +from feeds.views import import_opml urlpatterns = patterns( '', + url(r'^import_opml/?$', import_opml), ) diff --git a/feeds/views.py b/feeds/views.py index 08b5314..a53bb51 100644 --- a/feeds/views.py +++ b/feeds/views.py @@ -1,3 +1,4 @@ +# coding: utf-8 """ =========== feeds.views @@ -6,3 +7,82 @@ Views for feeds application. """ + +from lxml import etree + +from django.contrib.auth.decorators import login_required +from django.db import transaction +from django.middleware.csrf import get_token +from django.template.response import HttpResponse + +from feeds.models import Feed, Tag +from users.models import UserFeed, UserTag + + +@login_required +@transaction.commit_on_success +def import_opml(request): + user = request.user + if request.POST and request.FILES and 'opml_file' in request.FILES: + filename = request.FILES['opml_file'].temporary_file_path() + + parse_opml(filename, user) + + return HttpResponse('ok ' + get_token(request)) + + +def parse_opml(filename, user): + outlines_stack = [] + context = etree.iterparse(filename, events=("start", "end"), tag="outline") + + for action, elem in context: + if action == 'start': + parent = outlines_stack[-1] if outlines_stack else None + if 'xmlUrl' not in elem.attrib: + user_tag = add_tag(elem.attrib['title'], user, parent) + outlines_stack.append(user_tag) + else: + user_feed = add_feed( + elem.attrib['title'], elem.attrib['xmlUrl'], user, parent + ) + + outlines_stack.append(user_feed) + elif action == 'end': + if outlines_stack: + outlines_stack.pop() + + +def add_tag(name, user, parent): + tag, created = Tag.objects.get_or_create(name=name) + + if created: + tag.save() + + user_tag, created = UserTag.objects.get_or_create( + tag=tag, + user=user, + parent=parent if parent else None + ) + if created: + user_tag.save() + + return user_tag + + +def add_feed(title, href, user, parent): + feed, created = Feed.objects.get_or_create( + title=title, href=href, link=href + ) + + if created: + feed.save() + + user_feed, created = UserFeed.objects.get_or_create( + feed=feed, + user=user, + parent=parent if parent else None, + ) + if created: + user_feed.save() + + return user_feed diff --git a/requirements.txt b/requirements.txt index 268f740..4957267 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,6 +10,7 @@ flake8==2.0 gunicorn==0.17.4 honcho==0.4.2 ipython==0.13.2 +lxml==3.2.1 pep8==1.4.5 psycopg2==2.5.1 raven==3.3.12 diff --git a/users/admin.py b/users/admin.py index 365d7a6..0b730e3 100644 --- a/users/admin.py +++ b/users/admin.py @@ -9,7 +9,9 @@ from django.contrib import admin -from .models import User +from .models import User, UserTag, UserFeed admin.site.register(User) +admin.site.register(UserTag) +admin.site.register(UserFeed) diff --git a/users/migrations/0002_auto__add_userfeed__add_usertag.py b/users/migrations/0002_auto__add_userfeed__add_usertag.py new file mode 100644 index 0000000..22bffb6 --- /dev/null +++ b/users/migrations/0002_auto__add_userfeed__add_usertag.py @@ -0,0 +1,130 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding model 'UserFeed' + db.create_table('chitatel_users_feeds', ( + (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('feed', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['feeds.Feed'])), + ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['users.User'])), + ('parent', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='user_tags', null=True, to=orm['users.UserTag'])), + )) + db.send_create_signal(u'users', ['UserFeed']) + + # Adding model 'UserTag' + db.create_table('chitatel_users_tags', ( + (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('tag', self.gf('django.db.models.fields.related.ForeignKey')(related_name='user_tags', to=orm['feeds.Tag'])), + ('user', self.gf('django.db.models.fields.related.ForeignKey')(related_name='user_tags', to=orm['users.User'])), + ('parent', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='tags', null=True, to=orm['users.UserTag'])), + )) + db.send_create_signal(u'users', ['UserTag']) + + # Removing M2M table for field feeds on 'User' + db.delete_table(db.shorten_name('chitatel_user_feeds')) + + # Removing M2M table for field tags on 'User' + db.delete_table(db.shorten_name('chitatel_user_tags')) + + + def backwards(self, orm): + # Deleting model 'UserFeed' + db.delete_table('chitatel_users_feeds') + + # Deleting model 'UserTag' + db.delete_table('chitatel_users_tags') + + # Adding M2M table for field feeds on 'User' + m2m_table_name = db.shorten_name('chitatel_user_feeds') + db.create_table(m2m_table_name, ( + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), + ('user', models.ForeignKey(orm[u'users.user'], null=False)), + ('feed', models.ForeignKey(orm[u'feeds.feed'], null=False)) + )) + db.create_unique(m2m_table_name, ['user_id', 'feed_id']) + + # Adding M2M table for field tags on 'User' + m2m_table_name = db.shorten_name('chitatel_user_tags') + db.create_table(m2m_table_name, ( + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), + ('user', models.ForeignKey(orm[u'users.user'], null=False)), + ('tag', models.ForeignKey(orm[u'feeds.tag'], null=False)) + )) + db.create_unique(m2m_table_name, ['user_id', 'tag_id']) + + + models = { + u'auth.group': { + 'Meta': {'object_name': 'Group'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + u'auth.permission': { + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + u'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + u'feeds.feed': { + 'Meta': {'object_name': 'Feed', 'db_table': "'chitatel_feed'"}, + 'href': ('django.db.models.fields.URLField', [], {'unique': 'True', 'max_length': '255'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'link': ('django.db.models.fields.URLField', [], {'max_length': '255', 'blank': 'True'}), + 'summary': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'update_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}) + }, + u'feeds.tag': { + 'Meta': {'object_name': 'Tag', 'db_table': "'chitatel_tag'"}, + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32', 'primary_key': 'True'}) + }, + u'users.user': { + 'Meta': {'object_name': 'User', 'db_table': "'chitatel_user'"}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'feeds': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'users'", 'to': u"orm['feeds.Feed']", 'through': u"orm['users.UserFeed']", 'blank': 'True', 'symmetrical': 'False', 'null': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'users'", 'to': u"orm['feeds.Tag']", 'through': u"orm['users.UserTag']", 'blank': 'True', 'symmetrical': 'False', 'null': 'True'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + u'users.userfeed': { + 'Meta': {'object_name': 'UserFeed', 'db_table': "'chitatel_users_feeds'"}, + 'feed': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['feeds.Feed']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'user_tags'", 'null': 'True', 'to': u"orm['users.UserTag']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['users.User']"}) + }, + u'users.usertag': { + 'Meta': {'object_name': 'UserTag', 'db_table': "'chitatel_users_tags'"}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'tags'", 'null': 'True', 'to': u"orm['users.UserTag']"}), + 'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user_tags'", 'to': u"orm['feeds.Tag']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user_tags'", 'to': u"orm['users.User']"}) + } + } + + complete_apps = ['users'] \ No newline at end of file diff --git a/users/models.py b/users/models.py index 5e33281..1921eb2 100644 --- a/users/models.py +++ b/users/models.py @@ -19,14 +19,54 @@ class User(AbstractUser): """ feeds = models.ManyToManyField( 'feeds.Feed', blank=True, null=True, related_name='users', - verbose_name=_('feeds') + verbose_name=_('feeds'), + through='UserFeed' ) tags = models.ManyToManyField( 'feeds.Tag', blank=True, null=True, related_name='users', - verbose_name=_('tags') + verbose_name=_('tags'), + through='UserTag' ) class Meta: db_table = 'chitatel_user' verbose_name = _('user') verbose_name_plural = _('users') + + +class UserTag(models.Model): + tag = models.ForeignKey('feeds.Tag', related_name='user_tags') + user = models.ForeignKey(User, related_name='user_tags') + parent = models.ForeignKey( + 'self', null=True, blank=True, related_name='tags' + ) + + class Meta: + db_table = 'chitatel_users_tags' + verbose_name = _('user_tag') + verbose_name_plural = _('user_tag') + + def __unicode__(self): + """ + Unicode representation. + """ + return u"%s - %s" % (self.user, self.tag) + + +class UserFeed(models.Model): + feed = models.ForeignKey('feeds.Feed') + user = models.ForeignKey(User) + parent = models.ForeignKey( + UserTag, null=True, blank=True, related_name='user_tags' + ) + + class Meta: + db_table = 'chitatel_users_feeds' + verbose_name = _('user_feed') + verbose_name_plural = _('user_feed') + + def __unicode__(self): + """ + Unicode representation. + """ + return u"%s - %s" % (self.user, self.feed)