From 0f18ed8f5e179326721221df93734864074bc185 Mon Sep 17 00:00:00 2001 From: Elrond Date: Wed, 18 May 2011 00:44:10 +0200 Subject: Move models into new db/ directory The database is a central point of interest/discussion. Represent that by its own directory. This will surely become more interesting when we have migrations for example. --- mediagoblin/db/models.py | 128 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 mediagoblin/db/models.py (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py new file mode 100644 index 00000000..1bc1da60 --- /dev/null +++ b/mediagoblin/db/models.py @@ -0,0 +1,128 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011 Free Software Foundation, Inc +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import datetime, uuid + +from mongokit import Document, Set + +from mediagoblin import util +from mediagoblin.auth import lib as auth_lib +from mediagoblin import globals as mediagoblin_globals + +################### +# Custom validators +################### + +######## +# Models +######## + + +class User(Document): + __collection__ = 'users' + + structure = { + 'username': unicode, + 'email': unicode, + 'created': datetime.datetime, + 'plugin_data': dict, # plugins can dump stuff here. + 'pw_hash': unicode, + 'email_verified': bool, + 'status': unicode, + 'verification_key': unicode, + 'is_admin': bool, + } + + required_fields = ['username', 'created', 'pw_hash', 'email'] + + default_values = { + 'created': datetime.datetime.utcnow, + 'email_verified': False, + 'status': u'needs_email_verification', + 'verification_key': lambda: unicode(uuid.uuid4()), + 'is_admin': False} + + def check_login(self, password): + """ + See if a user can login with this password + """ + return auth_lib.bcrypt_check_password( + password, self['pw_hash']) + + +class MediaEntry(Document): + __collection__ = 'media_entries' + + structure = { + 'uploader': User, + 'title': unicode, + 'slug': unicode, + 'created': datetime.datetime, + 'description': unicode, + 'media_type': unicode, + 'media_data': dict, # extra data relevant to this media_type + 'plugin_data': dict, # plugins can dump stuff here. + 'tags': [unicode], + 'state': unicode, + + # For now let's assume there can only be one main file queued + # at a time + 'queued_media_file': [unicode], + + # A dictionary of logical names to filepaths + 'media_files': dict, + + # The following should be lists of lists, in appropriate file + # record form + 'attachment_files': list, + + # This one should just be a single file record + 'thumbnail_file': [unicode]} + + required_fields = [ + 'uploader', 'created', 'media_type'] + + default_values = { + 'created': datetime.datetime.utcnow, + 'state': u'unprocessed'} + + # Actually we should referene uniqueness by uploader, but we + # should fix http://bugs.foocorp.net/issues/340 first. + # indexes = [ + # {'fields': ['uploader', 'slug'], + # 'unique': True}] + + def main_mediafile(self): + pass + + def generate_slug(self): + self['slug'] = util.slugify(self['title']) + + duplicate = mediagoblin_globals.database.media_entries.find_one( + {'slug': self['slug']}) + + if duplicate: + self['slug'] = "%s-%s" % (self['_id'], self['slug']) + +REGISTER_MODELS = [MediaEntry, User] + + +def register_models(connection): + """ + Register all models in REGISTER_MODELS with this connection. + """ + connection.register(REGISTER_MODELS) + -- cgit v1.2.3 From 6926b23d43323bbc214aa285948ad2850c5ad22e Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Fri, 20 May 2011 18:16:10 -0500 Subject: Added a url_for_self method for generating mediaentry links This allows for optionally making the url based off of slugs or ids --- mediagoblin/db/models.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 1bc1da60..8e7889eb 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -117,6 +117,24 @@ class MediaEntry(Document): if duplicate: self['slug'] = "%s-%s" % (self['_id'], self['slug']) + def url_for_self(self, urlgen): + """ + Generate an appropriate url for ourselves + + Use a slug if we have one, else use our '_id'. + """ + if self.get('slug'): + return urlgen( + 'mediagoblin.user_pages.media_home', + user=self['uploader']['username'], + media=self['slug']) + else: + return urlgen( + 'mediagoblin.user_pages.media_home', + user=self['uploader']['username'], + media=unicode(self['_id'])) + + REGISTER_MODELS = [MediaEntry, User] -- cgit v1.2.3 From 757f37a52d7854ed752d56c66498383125a05a9f Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Sun, 22 May 2011 10:52:53 -0500 Subject: User migration works (but the rest of the system isn't updated for new user setup yet) --- mediagoblin/db/models.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 8e7889eb..3fc8d9e8 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -21,6 +21,8 @@ from mongokit import Document, Set from mediagoblin import util from mediagoblin.auth import lib as auth_lib from mediagoblin import globals as mediagoblin_globals +from mediagoblin.db import migrations +from mediagoblin.db.util import ObjectId ################### # Custom validators @@ -67,7 +69,7 @@ class MediaEntry(Document): __collection__ = 'media_entries' structure = { - 'uploader': User, + 'uploader': ObjectId, 'title': unicode, 'slug': unicode, 'created': datetime.datetime, @@ -99,6 +101,8 @@ class MediaEntry(Document): 'created': datetime.datetime.utcnow, 'state': u'unprocessed'} + migration_handler = migrations.MediaEntryMigration + # Actually we should referene uniqueness by uploader, but we # should fix http://bugs.foocorp.net/issues/340 first. # indexes = [ -- cgit v1.2.3 From 16509be160470202147d3b711126c7928790777d Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Sun, 22 May 2011 16:06:45 -0500 Subject: Update all the views so that they use the uploader reference instead of uploader embedding --- mediagoblin/db/models.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 3fc8d9e8..37420834 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -127,17 +127,22 @@ class MediaEntry(Document): Use a slug if we have one, else use our '_id'. """ + uploader = self.uploader() + if self.get('slug'): return urlgen( 'mediagoblin.user_pages.media_home', - user=self['uploader']['username'], + user=uploader['username'], media=self['slug']) else: return urlgen( 'mediagoblin.user_pages.media_home', - user=self['uploader']['username'], + user=uploader['username'], media=unicode(self['_id'])) + def uploader(self): + return self.db.User.find_one({'_id': self['uploader']}) + REGISTER_MODELS = [MediaEntry, User] -- cgit v1.2.3 From ce72a1bb15f421725696cc3eea28b94de098f8f2 Mon Sep 17 00:00:00 2001 From: Jakob Kramer Date: Sun, 29 May 2011 19:15:46 +0200 Subject: this should fix #354 --- mediagoblin/db/models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 37420834..0b4390d7 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -38,6 +38,7 @@ class User(Document): structure = { 'username': unicode, + 'username_repr': unicode, 'email': unicode, 'created': datetime.datetime, 'plugin_data': dict, # plugins can dump stuff here. @@ -48,7 +49,7 @@ class User(Document): 'is_admin': bool, } - required_fields = ['username', 'created', 'pw_hash', 'email'] + required_fields = ['username', 'username_repr', 'created', 'pw_hash', 'email'] default_values = { 'created': datetime.datetime.utcnow, -- cgit v1.2.3 From db5912e358043c90194c6409fa09ab07d16df4c3 Mon Sep 17 00:00:00 2001 From: Jakob Kramer Date: Sun, 29 May 2011 19:49:25 +0200 Subject: remove all 'username_repr' stuff --- mediagoblin/db/models.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 0b4390d7..37420834 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -38,7 +38,6 @@ class User(Document): structure = { 'username': unicode, - 'username_repr': unicode, 'email': unicode, 'created': datetime.datetime, 'plugin_data': dict, # plugins can dump stuff here. @@ -49,7 +48,7 @@ class User(Document): 'is_admin': bool, } - required_fields = ['username', 'username_repr', 'created', 'pw_hash', 'email'] + required_fields = ['username', 'created', 'pw_hash', 'email'] default_values = { 'created': datetime.datetime.utcnow, -- cgit v1.2.3 From b1ae76aea0141354b479e1c0451bc003e9b6e248 Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Mon, 30 May 2011 18:06:12 -0500 Subject: Add an index on MediaEntries making sure slugs + uploader combos are unique --- mediagoblin/db/models.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 37420834..3da97a49 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -95,7 +95,7 @@ class MediaEntry(Document): 'thumbnail_file': [unicode]} required_fields = [ - 'uploader', 'created', 'media_type'] + 'uploader', 'created', 'media_type', 'slug'] default_values = { 'created': datetime.datetime.utcnow, @@ -103,11 +103,10 @@ class MediaEntry(Document): migration_handler = migrations.MediaEntryMigration - # Actually we should referene uniqueness by uploader, but we - # should fix http://bugs.foocorp.net/issues/340 first. - # indexes = [ - # {'fields': ['uploader', 'slug'], - # 'unique': True}] + indexes = [ + # Referene uniqueness of slugs by uploader + {'fields': ['uploader', 'slug'], + 'unique': True}] def main_mediafile(self): pass -- cgit v1.2.3 From b93a6a229e1c7a7eef76e8322104912378f79a96 Mon Sep 17 00:00:00 2001 From: Aleksandar Micovic Date: Tue, 31 May 2011 17:14:23 -0400 Subject: Added the ability to regenerate a verification key. --- mediagoblin/db/models.py | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 37420834..0e933fb7 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -64,6 +64,14 @@ class User(Document): return auth_lib.bcrypt_check_password( password, self['pw_hash']) + def generate_new_verification_key(self): + """ + Create a new verification key, overwriting the old one. + """ + + self['verification_key'] = unicode(uuid.uuid4()) + self.save(validate=False) + class MediaEntry(Document): __collection__ = 'media_entries' -- cgit v1.2.3 From a77d952aa6659f0291f856495b18a43dd7e28508 Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Thu, 2 Jun 2011 09:02:13 -0500 Subject: No need for a method for generating the verification key as a method on the class, can just do that in the view --- mediagoblin/db/models.py | 8 -------- 1 file changed, 8 deletions(-) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 0b85430a..3da97a49 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -64,14 +64,6 @@ class User(Document): return auth_lib.bcrypt_check_password( password, self['pw_hash']) - def generate_new_verification_key(self): - """ - Create a new verification key, overwriting the old one. - """ - - self['verification_key'] = unicode(uuid.uuid4()) - self.save(validate=False) - class MediaEntry(Document): __collection__ = 'media_entries' -- cgit v1.2.3 From 44e2da2fe60a3b8765d0fef5a9ce0c3e5997dd01 Mon Sep 17 00:00:00 2001 From: Joar Wandborg Date: Sun, 12 Jun 2011 03:24:31 +0200 Subject: Added Markdown rendering for `media_entry` --- mediagoblin/db/models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 3da97a49..0b092d60 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -73,7 +73,8 @@ class MediaEntry(Document): 'title': unicode, 'slug': unicode, 'created': datetime.datetime, - 'description': unicode, + 'description': unicode, # May contain markdown/up + 'description_html': unicode, # May contain plaintext, or HTML 'media_type': unicode, 'media_data': dict, # extra data relevant to this media_type 'plugin_data': dict, # plugins can dump stuff here. -- cgit v1.2.3 From 6e7ce8d1af8c6fcf7d00992b1c8ef0e8c1602479 Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Sun, 12 Jun 2011 17:27:37 -0500 Subject: mediagoblin.globals->mediagoblin.mg_globals --- mediagoblin/db/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 3da97a49..d77cf619 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -20,7 +20,7 @@ from mongokit import Document, Set from mediagoblin import util from mediagoblin.auth import lib as auth_lib -from mediagoblin import globals as mediagoblin_globals +from mediagoblin import mg_globals from mediagoblin.db import migrations from mediagoblin.db.util import ObjectId @@ -114,7 +114,7 @@ class MediaEntry(Document): def generate_slug(self): self['slug'] = util.slugify(self['title']) - duplicate = mediagoblin_globals.database.media_entries.find_one( + duplicate = mg_globals.database.media_entries.find_one( {'slug': self['slug']}) if duplicate: -- cgit v1.2.3 From 279d925e757d9756e09e14316f0d85b6e20624ea Mon Sep 17 00:00:00 2001 From: cfdv Date: Sat, 18 Jun 2011 15:00:05 -0500 Subject: adds user bio and website url fields to the database --- mediagoblin/db/models.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index d77cf619..4b6518cc 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -46,6 +46,8 @@ class User(Document): 'status': unicode, 'verification_key': unicode, 'is_admin': bool, + 'website_url' : unicode, + 'bio' : unicode } required_fields = ['username', 'created', 'pw_hash', 'email'] -- cgit v1.2.3 From 630b57a366d10495a89d392c9b02cf432e6a1599 Mon Sep 17 00:00:00 2001 From: cfdv Date: Sat, 18 Jun 2011 16:42:22 -0500 Subject: baby step towards enabling profile edits adds * url and bio fields to database * form for editing the user profile * route to the edit profile controller * view for the profile editing page * template for the profile editing page * link to edit profile in the welcome page still needs * thorough inspection to see if it makes sense * tests * ? --- mediagoblin/db/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 4b6518cc..78bec979 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -46,7 +46,7 @@ class User(Document): 'status': unicode, 'verification_key': unicode, 'is_admin': bool, - 'website_url' : unicode, + 'url' : unicode, 'bio' : unicode } -- cgit v1.2.3 From 0472653ee4763e2f045632d49a9eed729a491f97 Mon Sep 17 00:00:00 2001 From: cfdv Date: Mon, 20 Jun 2011 22:47:43 -0500 Subject: assigns migration steps to User database objects adds the migration_handler to the User db class, connecting the migration steps in ../db/migrations.py to the migration code in gmg_commands --- mediagoblin/db/models.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index b3de7793..600b79ff 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -58,6 +58,8 @@ class User(Document): 'status': u'needs_email_verification', 'verification_key': lambda: unicode(uuid.uuid4()), 'is_admin': False} + + migration_handler = migrations.UserMigration def check_login(self, password): """ -- cgit v1.2.3 From c11f21ab3c64c6ef9d9cde8bfdcddf12f6663932 Mon Sep 17 00:00:00 2001 From: Joar Wandborg Date: Mon, 27 Jun 2011 23:39:40 +0200 Subject: Issue 362 - Add simple comments * Added MediaComment database model Holds `media_entry` (`ObjectId`), `author` (`ObjectId`), `created`, `content` and `content_html`. --- mediagoblin/db/models.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 600b79ff..5d00aa34 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -147,8 +147,32 @@ class MediaEntry(Document): def uploader(self): return self.db.User.find_one({'_id': self['uploader']}) +class MediaComment(Document): + __collection__ = 'media_comments' -REGISTER_MODELS = [MediaEntry, User] + structure = { + 'media_entry': ObjectId, + 'author': ObjectId, + 'created': datetime.datetime, + 'content': unicode, + 'content_html': unicode} + + required_fields = [ + 'author', 'created', 'content'] + + default_values = { + 'created': datetime.datetime.utcnow} + + def media_entry(self): + pass + + def author(self): + return self.db.User.find_one({'_id': self['author']}) + +REGISTER_MODELS = [ + MediaEntry, + User, + MediaComment] def register_models(connection): -- cgit v1.2.3 From b1db6f20dd3cdef9da9ad27513aeee2738c85262 Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Mon, 27 Jun 2011 16:56:41 -0500 Subject: Adding our current indexes and removing the index that was in models.py --- mediagoblin/db/models.py | 5 ----- 1 file changed, 5 deletions(-) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 600b79ff..8d06ae49 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -108,11 +108,6 @@ class MediaEntry(Document): migration_handler = migrations.MediaEntryMigration - indexes = [ - # Referene uniqueness of slugs by uploader - {'fields': ['uploader', 'slug'], - 'unique': True}] - def main_mediafile(self): pass -- cgit v1.2.3 From 7bd8197f32f17466f14d730546a06166ed6da67a Mon Sep 17 00:00:00 2001 From: Joar Wandborg Date: Wed, 29 Jun 2011 01:16:51 +0200 Subject: Issue #362 - Updated the MediaComment model * `MediaComment.get_comments()` now uses pagination * `MediaComment.get_comments()` now sorts by `created` DESC * `MediaComment.media_entry` is now **required** * `MediaComment.media_entry()` now returns parent `MediaEntry` --- mediagoblin/db/models.py | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 5d00aa34..5196dede 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -22,7 +22,8 @@ from mediagoblin import util from mediagoblin.auth import lib as auth_lib from mediagoblin import mg_globals from mediagoblin.db import migrations -from mediagoblin.db.util import ObjectId +from mediagoblin.db.util import DESCENDING, ObjectId +from mediagoblin.util import Pagination ################### # Custom validators @@ -115,7 +116,22 @@ class MediaEntry(Document): def main_mediafile(self): pass - + + def get_comments(self, page): + cursor = self.db.MediaComment.find({ + 'media_entry': self['_id']}).sort('created', DESCENDING) + + pagination = Pagination(page, cursor) + comments = pagination() + + data = list() + for comment in comments: + comment['author'] = self.db.User.find_one({ + '_id': comment['author']}) + data.append(comment) + + return (data, pagination) + def generate_slug(self): self['slug'] = util.slugify(self['title']) @@ -158,13 +174,13 @@ class MediaComment(Document): 'content_html': unicode} required_fields = [ - 'author', 'created', 'content'] + 'media_entry', 'author', 'created', 'content'] default_values = { 'created': datetime.datetime.utcnow} def media_entry(self): - pass + return self.db.MediaEntry.find_one({'_id': self['media_entry']}) def author(self): return self.db.User.find_one({'_id': self['author']}) -- cgit v1.2.3 From 6f59a3a32470b4d83ab94fe7c4dae83943500329 Mon Sep 17 00:00:00 2001 From: Joar Wandborg Date: Fri, 1 Jul 2011 15:26:29 +0200 Subject: Issue #362 - Simple comments - Changes based on feedback recieved from #mediagoblin * `db.models` - Removed `MediaEntry.get_comments()` and replaced it with a helper which just returns a cursor for the comments query * `media.html` - Added `{% set comment_author = comment.author() %}` * `user_pages.views` - media_home() now passes `MediaEntry.get_comments()` directly to `Pagination`, handles pagination for comments. * Added `MEDIA_COMMENTS_PER_PAGE` to define the number of comments per page in the `media_home()` view. --- mediagoblin/db/models.py | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index bf825a23..1d91a14b 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -23,7 +23,6 @@ from mediagoblin.auth import lib as auth_lib from mediagoblin import mg_globals from mediagoblin.db import migrations from mediagoblin.db.util import DESCENDING, ObjectId -from mediagoblin.util import Pagination ################### # Custom validators @@ -109,24 +108,13 @@ class MediaEntry(Document): migration_handler = migrations.MediaEntryMigration + def get_comments(self): + return self.db.MediaComment.find({ + 'media_entry': self['_id']}).sort('created', DESCENDING) + def main_mediafile(self): pass - - def get_comments(self, page): - cursor = self.db.MediaComment.find({ - 'media_entry': self['_id']}).sort('created', DESCENDING) - - pagination = Pagination(page, cursor) - comments = pagination() - - data = list() - for comment in comments: - comment['author'] = self.db.User.find_one({ - '_id': comment['author']}) - data.append(comment) - - return (data, pagination) - + def generate_slug(self): self['slug'] = util.slugify(self['title']) -- cgit v1.2.3 From 9c0fe63fadc848b5154c7c1d4b2ff72dd05bc1c6 Mon Sep 17 00:00:00 2001 From: Caleb Forbes Davis V Date: Sat, 2 Jul 2011 06:15:58 -0500 Subject: adds previous and next links in the sidebar Feature #401 - previous/next navigation on media pages * media.html includes a new prev_next.html template containing the links * prev_next.html calls functions added to the media model to retrieve the appropriate objects from the database, formatted with urlgen * a small change to util.py brings ASCENDING into the mix --- mediagoblin/db/models.py | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index bf825a23..cfd83430 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -22,7 +22,7 @@ from mediagoblin import util from mediagoblin.auth import lib as auth_lib from mediagoblin import mg_globals from mediagoblin.db import migrations -from mediagoblin.db.util import DESCENDING, ObjectId +from mediagoblin.db.util import ASCENDING, DESCENDING, ObjectId from mediagoblin.util import Pagination ################### @@ -154,6 +154,32 @@ class MediaEntry(Document): 'mediagoblin.user_pages.media_home', user=uploader['username'], media=unicode(self['_id'])) + + def url_to_prev(self, urlgen): + """ + Provide a url to the previous entry from this user, if there is one + """ + cursor = self.db.MediaEntry.find({'_id' : {"$lt": self['_id']}, + 'uploader': self['uploader']}).sort( + '_id', DESCENDING).limit(1) + + if cursor.count(): + return urlgen('mediagoblin.user_pages.media_home', + user=self.uploader()['username'], + media=unicode(cursor[0]['_id'])) + + def url_to_next(self, urlgen): + """ + Provide a url to the next entry from this user, if there is one + """ + cursor = self.db.MediaEntry.find({'_id' : {"$gt": self['_id']}, + 'uploader': self['uploader']}).sort( + '_id', ASCENDING).limit(1) + + if cursor.count(): + return urlgen('mediagoblin.user_pages.media_home', + user=self.uploader()['username'], + media=unicode(cursor[0]['_id'])) def uploader(self): return self.db.User.find_one({'_id': self['uploader']}) -- cgit v1.2.3 From 2c9e635ae2ecbef0649df78636503be357f16a7f Mon Sep 17 00:00:00 2001 From: Joar Wandborg Date: Sun, 3 Jul 2011 05:46:00 +0200 Subject: Feature #400 - Resize images to fit on page - Additions * `migrations.py` * Removed empty line * Added empty line * `models.py` * Added `MediaEntry.get_display_media()` helper function * `process_media.__init__.py` * Updated `process_media_initial()` * Renamed `main` => `original`. * Added condition to `medium`, it's only created if the original dimensions exceed the MEDIUM_SIZE dimensions. * `media.html` * The image tag is now populated by `MediaEntry.get_display_media()` * `util.py` * Added `DISPLAY_IMAGE_FETCHING_ORDER`, used by `MediaEntry.get_display_media()` --- mediagoblin/db/models.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index bf825a23..3190d8fa 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -24,6 +24,7 @@ from mediagoblin import mg_globals from mediagoblin.db import migrations from mediagoblin.db.util import DESCENDING, ObjectId from mediagoblin.util import Pagination +from mediagoblin.util import DISPLAY_IMAGE_FETCHING_ORDER ################### # Custom validators @@ -109,6 +110,24 @@ class MediaEntry(Document): migration_handler = migrations.MediaEntryMigration + def get_display_media(self, media_map, fetch_order=DISPLAY_IMAGE_FETCHING_ORDER): + """ + Find the best media for display. + + Args: + - media_map: a dict like + {u'image_size': [u'dir1', u'dir2', u'image.jpg']} + - fetch_order: the order we should try fetching images in + + Returns: + (media_size, media_path) + """ + media_sizes = media_map.keys() + print media_sizes + for media_size in DISPLAY_IMAGE_FETCHING_ORDER: + if media_size in media_sizes: + return media_map[media_size] + def main_mediafile(self): pass -- cgit v1.2.3 From 380ac094f6ebcf7b457d98a86ac7cc0af2347ba4 Mon Sep 17 00:00:00 2001 From: Joar Wandborg Date: Sun, 3 Jul 2011 06:37:40 +0200 Subject: Removed debug code, erroneously included in my last commit --- mediagoblin/db/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 3190d8fa..2d6a71f7 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -123,7 +123,7 @@ class MediaEntry(Document): (media_size, media_path) """ media_sizes = media_map.keys() - print media_sizes + for media_size in DISPLAY_IMAGE_FETCHING_ORDER: if media_size in media_sizes: return media_map[media_size] -- cgit v1.2.3 From 77b958018b8ef6343394b8f138e52944334a5e1c Mon Sep 17 00:00:00 2001 From: Caleb Forbes Davis V Date: Mon, 4 Jul 2011 20:24:57 -0500 Subject: Feature #423 - gallery and scroll image ordering match --- mediagoblin/db/models.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 8aa35ca9..e764d368 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -147,9 +147,9 @@ class MediaEntry(Document): """ Provide a url to the previous entry from this user, if there is one """ - cursor = self.db.MediaEntry.find({'_id' : {"$lt": self['_id']}, + cursor = self.db.MediaEntry.find({'_id' : {"$gt": self['_id']}, 'uploader': self['uploader']}).sort( - '_id', DESCENDING).limit(1) + '_id', ASCENDING).limit(1) if cursor.count(): return urlgen('mediagoblin.user_pages.media_home', @@ -160,9 +160,9 @@ class MediaEntry(Document): """ Provide a url to the next entry from this user, if there is one """ - cursor = self.db.MediaEntry.find({'_id' : {"$gt": self['_id']}, + cursor = self.db.MediaEntry.find({'_id' : {"$lt": self['_id']}, 'uploader': self['uploader']}).sort( - '_id', ASCENDING).limit(1) + '_id', DESCENDING).limit(1) if cursor.count(): return urlgen('mediagoblin.user_pages.media_home', -- cgit v1.2.3 From ce2ac488263470270dea8619c88de648831b06ec Mon Sep 17 00:00:00 2001 From: Caleb Forbes Davis V Date: Tue, 5 Jul 2011 21:33:02 -0500 Subject: f#435 - avoids linking to unprocessed media in prev and next --- mediagoblin/db/models.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 8aa35ca9..3bd1f61f 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -148,7 +148,8 @@ class MediaEntry(Document): Provide a url to the previous entry from this user, if there is one """ cursor = self.db.MediaEntry.find({'_id' : {"$lt": self['_id']}, - 'uploader': self['uploader']}).sort( + 'uploader': self['uploader'], + 'state': 'processed'}).sort( '_id', DESCENDING).limit(1) if cursor.count(): @@ -161,7 +162,8 @@ class MediaEntry(Document): Provide a url to the next entry from this user, if there is one """ cursor = self.db.MediaEntry.find({'_id' : {"$gt": self['_id']}, - 'uploader': self['uploader']}).sort( + 'uploader': self['uploader'], + 'state': 'processed'}).sort( '_id', ASCENDING).limit(1) if cursor.count(): -- cgit v1.2.3 From b1db2c2e744dc0e5fcd10b53792c6ce306fc7149 Mon Sep 17 00:00:00 2001 From: Caleb Forbes Davis V Date: Tue, 5 Jul 2011 21:40:00 -0500 Subject: slug-style urls in previous and next urls look much better Bug #434 - identifies media by slug instead of _id in prev/next --- mediagoblin/db/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 8aa35ca9..c7506dbb 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -154,7 +154,7 @@ class MediaEntry(Document): if cursor.count(): return urlgen('mediagoblin.user_pages.media_home', user=self.uploader()['username'], - media=unicode(cursor[0]['_id'])) + media=unicode(cursor[0]['slug'])) def url_to_next(self, urlgen): """ @@ -167,7 +167,7 @@ class MediaEntry(Document): if cursor.count(): return urlgen('mediagoblin.user_pages.media_home', user=self.uploader()['username'], - media=unicode(cursor[0]['_id'])) + media=unicode(cursor[0]['slug'])) def uploader(self): return self.db.User.find_one({'_id': self['uploader']}) -- cgit v1.2.3 From e6fd112d429d1fcc5994ff19c61bd67367a33ce5 Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Thu, 7 Jul 2011 08:22:12 -0500 Subject: This should actually fix the next and previous buttons now. Sorry I borked the merge! --- mediagoblin/db/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 2b7933a4..279cb9f2 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -147,7 +147,7 @@ class MediaEntry(Document): """ Provide a url to the previous entry from this user, if there is one """ - cursor = self.db.MediaEntry.find({'_id' : {"$lt": self['_id']}, + cursor = self.db.MediaEntry.find({'_id' : {"$gt": self['_id']}, 'uploader': self['uploader'], 'state': 'processed'}).sort( '_id', ASCENDING).limit(1) @@ -160,7 +160,7 @@ class MediaEntry(Document): """ Provide a url to the next entry from this user, if there is one """ - cursor = self.db.MediaEntry.find({'_id' : {"$gt": self['_id']}, + cursor = self.db.MediaEntry.find({'_id' : {"$lt": self['_id']}, 'uploader': self['uploader'], 'state': 'processed'}).sort( '_id', DESCENDING).limit(1) -- cgit v1.2.3 From 4c465852d198a1d8e8562a1e25b23fa7c9b4d2b4 Mon Sep 17 00:00:00 2001 From: Aaron Williamson Date: Sun, 10 Jul 2011 22:28:48 -0400 Subject: Markdown-enable user bio (Feature 410) --- mediagoblin/db/models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 279cb9f2..8e07e738 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -47,7 +47,8 @@ class User(Document): 'verification_key': unicode, 'is_admin': bool, 'url' : unicode, - 'bio' : unicode + 'bio' : unicode, # May contain markdown + 'bio_html': unicode, # May contain plaintext, or HTML } required_fields = ['username', 'created', 'pw_hash', 'email'] -- cgit v1.2.3 From c2ddd85e4ac6c50289711fb5673dd716329a569b Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Mon, 11 Jul 2011 21:09:36 -0500 Subject: Removing old style migrations... not in use anymore --- mediagoblin/db/models.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 279cb9f2..918dee0e 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -16,12 +16,11 @@ import datetime, uuid -from mongokit import Document, Set +from mongokit import Document from mediagoblin import util from mediagoblin.auth import lib as auth_lib from mediagoblin import mg_globals -from mediagoblin.db import migrations from mediagoblin.db.util import ASCENDING, DESCENDING, ObjectId ################### @@ -59,8 +58,6 @@ class User(Document): 'verification_key': lambda: unicode(uuid.uuid4()), 'is_admin': False} - migration_handler = migrations.UserMigration - def check_login(self, password): """ See if a user can login with this password @@ -106,8 +103,6 @@ class MediaEntry(Document): 'created': datetime.datetime.utcnow, 'state': u'unprocessed'} - migration_handler = migrations.MediaEntryMigration - def get_comments(self): return self.db.MediaComment.find({ 'media_entry': self['_id']}).sort('created', DESCENDING) @@ -196,6 +191,7 @@ class MediaComment(Document): def author(self): return self.db.User.find_one({'_id': self['author']}) + REGISTER_MODELS = [ MediaEntry, User, -- cgit v1.2.3 From 16bcd1e71494700fba04406c0500a6bb32423d8a Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Sun, 17 Jul 2011 15:48:44 -0500 Subject: Docstring for the User model's various schema fields --- mediagoblin/db/models.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 9d7bcf6b..763e4d68 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -37,6 +37,32 @@ from mediagoblin.util import DISPLAY_IMAGE_FETCHING_ORDER class User(Document): + """ + A user of MediaGoblin. + + Structure: + - username: The username of this user, should be unique to this instance. + - email: Email address of this user + - created: When the user was created + - plugin_data: a mapping of extra plugin information for this User. + Nothing uses this yet as we don't have plugins, but someday we + might... :) + - pw_hash: Hashed version of user's password. + - email_verified: Whether or not the user has verified their email or not. + Most parts of the site are disabled for users who haven't yet. + - status: whether or not the user is active, etc. Currently only has two + values, 'needs_email_verification' or 'active'. (In the future, maybe + we'll change this to a boolean with a key of 'active' and have a + separate field for a reason the user's been disabled if that's + appropriate... email_verified is already separate, after all.) + - verification_key: If the user is awaiting email verification, the user + will have to provide this key (which will be encoded in the presented + URL) in order to confirm their email as active. + - is_admin: Whether or not this user is an administrator or not. + - url: this user's personal webpage/website, if appropriate. + - bio: biography of this user (plaintext, in markdown) + - bio_html: biography of the user converted to proper HTML. + """ __collection__ = 'users' structure = { -- cgit v1.2.3 From 080a81ec48b9681ed19adb998340585ed8716c77 Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Sun, 17 Jul 2011 16:51:36 -0500 Subject: Described the MediaEntry structure in the docstring. --- mediagoblin/db/models.py | 87 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 81 insertions(+), 6 deletions(-) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 763e4d68..db755402 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -88,7 +88,7 @@ class User(Document): 'status': u'needs_email_verification', 'verification_key': lambda: unicode(uuid.uuid4()), 'is_admin': False} - + def check_login(self, password): """ See if a user can login with this password @@ -98,6 +98,81 @@ class User(Document): class MediaEntry(Document): + """ + Record of a piece of media. + + Structure: + - uploader: A reference to a User who uploaded this. + + - title: Title of this work + + - slug: A normalized "slug" which can be used as part of a URL to retrieve + this work, such as 'my-works-name-in-slug-form' may be viewable by + 'http://mg.example.org/u/username/m/my-works-name-in-slug-form/' + Note that since URLs are constructed this way, slugs must be unique + per-uploader. (An index is provided to enforce that but code should be + written on the python side to ensure this as well.) + + - created: Date and time of when this piece of work was uploaded. + + - description: Uploader-set description of this work. This can be marked + up with MarkDown for slight fanciness (links, boldness, italics, + paragraphs...) + + - description_html: Rendered version of the description, run through + Markdown and cleaned with our cleaning tool. + + - media_type: What type of media is this? Currently we only support + 'image' ;) + + - media_data: Extra information that's media-format-dependent. + For example, images might contain some EXIF data that's not appropriate + to other formats. You might store it like: + + mediaentry['media_data']['exif'] = { + 'manufacturer': 'CASIO', + 'model': 'QV-4000', + 'exposure_time': .659} + + Alternately for video you might store: + + # play length in seconds + mediaentry['media_data']['play_length'] = 340 + + ... so what's appropriate here really depends on the media type. + + - plugin_data: a mapping of extra plugin information for this User. + Nothing uses this yet as we don't have plugins, but someday we + might... :) + + - tags: A list of tags. Each tag is stored as a dictionary that has a key + for the actual name and the normalized name-as-slug, so ultimately this + looks like: + [{'name': 'Gully Gardens', + 'slug': 'gully-gardens'}, + {'name': 'Castle Adventure Time?!", + 'slug': 'castle-adventure-time'}] + + - state: What's the state of this file? Active, inactive, disabled, etc... + But really for now there are only two states: + "unprocessed": uploaded but needs to go through processing for display + "processed": processed and able to be displayed + + - queued_media_file: storage interface style filepath describing a file + queued for processing. This is stored in the mg_globals.queue_store + storage system. + + - media_files: Files relevant to this that have actually been processed + and are available for various types of display. Stored like: + {'thumb': ['dir1', 'dir2', 'pic.png'} + + - attachment_files: A list of "attachment" files, ones that aren't + critical to this piece of media but may be usefully relevant to people + viewing the work. (currently unused.) + + - thumbnail_file: Deprecated... we should remove this ;) + + """ __collection__ = 'media_entries' structure = { @@ -164,7 +239,7 @@ class MediaEntry(Document): duplicate = mg_globals.database.media_entries.find_one( {'slug': self['slug']}) - + if duplicate: self['slug'] = "%s-%s" % (self['_id'], self['slug']) @@ -186,12 +261,12 @@ class MediaEntry(Document): 'mediagoblin.user_pages.media_home', user=uploader['username'], media=unicode(self['_id'])) - + def url_to_prev(self, urlgen): """ Provide a url to the previous entry from this user, if there is one """ - cursor = self.db.MediaEntry.find({'_id' : {"$gt": self['_id']}, + cursor = self.db.MediaEntry.find({'_id' : {"$gt": self['_id']}, 'uploader': self['uploader'], 'state': 'processed'}).sort( '_id', ASCENDING).limit(1) @@ -199,12 +274,12 @@ class MediaEntry(Document): return urlgen('mediagoblin.user_pages.media_home', user=self.uploader()['username'], media=unicode(cursor[0]['slug'])) - + def url_to_next(self, urlgen): """ Provide a url to the next entry from this user, if there is one """ - cursor = self.db.MediaEntry.find({'_id' : {"$lt": self['_id']}, + cursor = self.db.MediaEntry.find({'_id' : {"$lt": self['_id']}, 'uploader': self['uploader'], 'state': 'processed'}).sort( '_id', DESCENDING).limit(1) -- cgit v1.2.3 From e83dc091cc8d60f5dd7372b0d684f16fbfbc7fec Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Sun, 17 Jul 2011 17:06:06 -0500 Subject: docstring for MediaComment's structure --- mediagoblin/db/models.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index db755402..bad15aca 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -171,7 +171,6 @@ class MediaEntry(Document): viewing the work. (currently unused.) - thumbnail_file: Deprecated... we should remove this ;) - """ __collection__ = 'media_entries' @@ -294,6 +293,18 @@ class MediaEntry(Document): class MediaComment(Document): + """ + A comment on a MediaEntry. + + Structure: + - media_entry: The media entry this comment is attached to + - author: user who posted this comment + - created: when the comment was created + - content: plaintext (but markdown'able) version of the comment's content. + - content_html: the actual html-rendered version of the comment displayed. + Run through Markdown and the HTML cleaner. + """ + __collection__ = 'media_comments' structure = { -- cgit v1.2.3 From 6b9ee0ca13b99ee20f9d0c680a950c6a7494a5a0 Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Sun, 24 Jul 2011 23:12:46 -0500 Subject: Store the task id of a processing action in the database. --- mediagoblin/db/models.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index bad15aca..e97dc537 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -162,6 +162,8 @@ class MediaEntry(Document): queued for processing. This is stored in the mg_globals.queue_store storage system. + - queued_task_id: celery task id. Use this to fetch the task state. + - media_files: Files relevant to this that have actually been processed and are available for various types of display. Stored like: {'thumb': ['dir1', 'dir2', 'pic.png'} @@ -190,6 +192,7 @@ class MediaEntry(Document): # For now let's assume there can only be one main file queued # at a time 'queued_media_file': [unicode], + 'queued_task_id': unicode, # A dictionary of logical names to filepaths 'media_files': dict, -- cgit v1.2.3 From 0712a06dc6d6b05cb78d4b10af12c051e8f765e3 Mon Sep 17 00:00:00 2001 From: Caleb Forbes Davis V Date: Wed, 27 Jul 2011 14:42:09 -0500 Subject: changes tags to a list of dicts in the db, adding tag slugs - adds a function to convert the tag list of dicts to a text string properly delimited for loading into forms - tag string conversion function updated to generate list of dicts - updates all mentions of the conversion of the string to the tags db object - adds a tags template utility and updates the media template accordingly --- mediagoblin/db/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 279cb9f2..8fcbb208 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -82,7 +82,7 @@ class MediaEntry(Document): 'media_type': unicode, 'media_data': dict, # extra data relevant to this media_type 'plugin_data': dict, # plugins can dump stuff here. - 'tags': [unicode], + 'tags': [dict], 'state': unicode, # For now let's assume there can only be one main file queued -- cgit v1.2.3 From 84abd2bbc43e2d92d429c679f49e237207057150 Mon Sep 17 00:00:00 2001 From: Caleb Forbes Davis V Date: Wed, 10 Aug 2011 12:48:23 -0500 Subject: Bug #372 - MediaEntry.thumbnail_file not used - deleted the thumbnail_file from the media_entries collection - added a migration to remove the field from previous db versions --- mediagoblin/db/models.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 4ef2d928..aff2a65b 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -169,8 +169,6 @@ class MediaEntry(Document): - attachment_files: A list of "attachment" files, ones that aren't critical to this piece of media but may be usefully relevant to people viewing the work. (currently unused.) - - - thumbnail_file: Deprecated... we should remove this ;) """ __collection__ = 'media_entries' @@ -196,10 +194,7 @@ class MediaEntry(Document): # The following should be lists of lists, in appropriate file # record form - 'attachment_files': list, - - # This one should just be a single file record - 'thumbnail_file': [unicode]} + 'attachment_files': list} required_fields = [ 'uploader', 'created', 'media_type', 'slug'] -- cgit v1.2.3 From 6c50c2106816c920ef404dea641a8eac8c5914eb Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Sat, 13 Aug 2011 07:48:34 -0500 Subject: Add fail_error and fail_metadata fields to MediaEntry and relevant migration --- mediagoblin/db/models.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 0dcb6ce8..982883d7 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -171,6 +171,9 @@ class MediaEntry(Document): - attachment_files: A list of "attachment" files, ones that aren't critical to this piece of media but may be usefully relevant to people viewing the work. (currently unused.) + + - fail_error: path to the exception raised + - fail_metadata: """ __collection__ = 'media_entries' @@ -197,7 +200,12 @@ class MediaEntry(Document): # The following should be lists of lists, in appropriate file # record form - 'attachment_files': list} + 'attachment_files': list, + + # If things go badly in processing things, we'll store that + # data here + 'fail_error': unicode, + 'fail_metadata': dict} required_fields = [ 'uploader', 'created', 'media_type', 'slug'] -- cgit v1.2.3 From 6ee9c719025f954bfc996f11b4a89219f635a17f Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Sun, 14 Aug 2011 07:55:08 -0500 Subject: Method to get the failure exception object for a MediaEntry, if appropriate. --- mediagoblin/db/models.py | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 982883d7..b6e52441 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -297,6 +297,13 @@ class MediaEntry(Document): def uploader(self): return self.db.User.find_one({'_id': self['uploader']}) + def get_fail_exception(self): + """ + Get the exception that's appropriate for this error + """ + if self['fail_error']: + return util.import_component(self['fail_error']) + class MediaComment(Document): """ -- cgit v1.2.3 From 25ba955e20e9262f2599a21d234511b724569717 Mon Sep 17 00:00:00 2001 From: Alejandro Villanueva Date: Thu, 21 Jul 2011 11:55:41 -0500 Subject: Adding fotgot password functionality --- mediagoblin/db/models.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index b6e52441..a626937d 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -78,6 +78,8 @@ class User(Document): 'url' : unicode, 'bio' : unicode, # May contain markdown 'bio_html': unicode, # May contain plaintext, or HTML + 'fp_token': unicode, # forgotten password verification key + 'fp_token_expire': datetime.datetime } required_fields = ['username', 'created', 'pw_hash', 'email'] -- cgit v1.2.3 From 65030735085782be067c8c97e288e9baf3dbdbf4 Mon Sep 17 00:00:00 2001 From: Caleb Forbes Davis V Date: Sun, 28 Aug 2011 21:13:07 -0500 Subject: oops, uses Alejandro's fp_verification_key. my bad. --- mediagoblin/db/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index a626937d..ad989f81 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -78,7 +78,7 @@ class User(Document): 'url' : unicode, 'bio' : unicode, # May contain markdown 'bio_html': unicode, # May contain plaintext, or HTML - 'fp_token': unicode, # forgotten password verification key + 'fp_verification_key': unicode, # forgotten password verification key 'fp_token_expire': datetime.datetime } -- cgit v1.2.3 From 12a100e4d8bdda7bd2353403a7e08e3a94669498 Mon Sep 17 00:00:00 2001 From: Will Kahn-Greene Date: Thu, 1 Sep 2011 20:49:54 -0400 Subject: 508. Updates copyright/license information --- mediagoblin/db/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index b6e52441..792a515e 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -1,5 +1,5 @@ # GNU MediaGoblin -- federated, autonomous media hosting -# Copyright (C) 2011 Free Software Foundation, Inc +# Copyright (C) 2011 MediaGoblin contributors. See AUTHORS. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by -- cgit v1.2.3 From ae3bc7fabf8e0abb5f3d8b6534ca451890bbe90b Mon Sep 17 00:00:00 2001 From: Aaron Williamson Date: Sat, 1 Oct 2011 09:31:42 -0400 Subject: Moved common, translation, template, and url code out of util.py and into tools/[file].py --- mediagoblin/db/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index bbddada6..eacc801c 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -25,7 +25,7 @@ from mediagoblin.db import migrations from mediagoblin.db.util import ASCENDING, DESCENDING, ObjectId from mediagoblin.util import Pagination from mediagoblin.util import DISPLAY_IMAGE_FETCHING_ORDER - +from mediagoblin.tools import url ################### # Custom validators @@ -242,7 +242,7 @@ class MediaEntry(Document): pass def generate_slug(self): - self['slug'] = util.slugify(self['title']) + self['slug'] = url.slugify(self['title']) duplicate = mg_globals.database.media_entries.find_one( {'slug': self['slug']}) -- cgit v1.2.3 From 152a3bfaa36d58e44979f217c5799531f780250f Mon Sep 17 00:00:00 2001 From: Aaron Williamson Date: Sat, 1 Oct 2011 18:05:44 -0400 Subject: Finished splitting util.py into separate files. --- mediagoblin/db/models.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index eacc801c..0f5174cc 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -18,14 +18,12 @@ import datetime, uuid from mongokit import Document -from mediagoblin import util from mediagoblin.auth import lib as auth_lib from mediagoblin import mg_globals from mediagoblin.db import migrations from mediagoblin.db.util import ASCENDING, DESCENDING, ObjectId -from mediagoblin.util import Pagination -from mediagoblin.util import DISPLAY_IMAGE_FETCHING_ORDER -from mediagoblin.tools import url +from mediagoblin.tools.pagination import Pagination +from mediagoblin.tools import url, common ################### # Custom validators @@ -220,7 +218,7 @@ class MediaEntry(Document): return self.db.MediaComment.find({ 'media_entry': self['_id']}).sort('created', DESCENDING) - def get_display_media(self, media_map, fetch_order=DISPLAY_IMAGE_FETCHING_ORDER): + def get_display_media(self, media_map, fetch_order=common.DISPLAY_IMAGE_FETCHING_ORDER): """ Find the best media for display. @@ -234,7 +232,7 @@ class MediaEntry(Document): """ media_sizes = media_map.keys() - for media_size in DISPLAY_IMAGE_FETCHING_ORDER: + for media_size in common.DISPLAY_IMAGE_FETCHING_ORDER: if media_size in media_sizes: return media_map[media_size] @@ -304,7 +302,7 @@ class MediaEntry(Document): Get the exception that's appropriate for this error """ if self['fail_error']: - return util.import_component(self['fail_error']) + return common.import_component(self['fail_error']) class MediaComment(Document): -- cgit v1.2.3 From 243c3843bd574129caa7663e25d1a843b2d2dd30 Mon Sep 17 00:00:00 2001 From: Nathan Yergler Date: Sat, 1 Oct 2011 15:10:02 -0700 Subject: Whitespace and formatting cleanup. * Removed trailing whitespace * Line length < 80 where possible * Honor conventions on number of blank lines * Honor conventions about spaces around :, = --- mediagoblin/db/models.py | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index bbddada6..42db3f83 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -14,7 +14,8 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -import datetime, uuid +import datetime +import uuid from mongokit import Document @@ -69,17 +70,17 @@ class User(Document): 'username': unicode, 'email': unicode, 'created': datetime.datetime, - 'plugin_data': dict, # plugins can dump stuff here. + 'plugin_data': dict, # plugins can dump stuff here. 'pw_hash': unicode, 'email_verified': bool, 'status': unicode, 'verification_key': unicode, 'is_admin': bool, - 'url' : unicode, - 'bio' : unicode, # May contain markdown - 'bio_html': unicode, # May contain plaintext, or HTML - 'fp_verification_key': unicode, # forgotten password verification key - 'fp_token_expire': datetime.datetime + 'url': unicode, + 'bio': unicode, # May contain markdown + 'bio_html': unicode, # May contain plaintext, or HTML + 'fp_verification_key': unicode, # forgotten password verification key + 'fp_token_expire': datetime.datetime, } required_fields = ['username', 'created', 'pw_hash', 'email'] @@ -174,8 +175,8 @@ class MediaEntry(Document): critical to this piece of media but may be usefully relevant to people viewing the work. (currently unused.) - - fail_error: path to the exception raised - - fail_metadata: + - fail_error: path to the exception raised + - fail_metadata: """ __collection__ = 'media_entries' @@ -184,11 +185,11 @@ class MediaEntry(Document): 'title': unicode, 'slug': unicode, 'created': datetime.datetime, - 'description': unicode, # May contain markdown/up - 'description_html': unicode, # May contain plaintext, or HTML + 'description': unicode, # May contain markdown/up + 'description_html': unicode, # May contain plaintext, or HTML 'media_type': unicode, - 'media_data': dict, # extra data relevant to this media_type - 'plugin_data': dict, # plugins can dump stuff here. + 'media_data': dict, # extra data relevant to this media_type + 'plugin_data': dict, # plugins can dump stuff here. 'tags': [dict], 'state': unicode, @@ -220,7 +221,8 @@ class MediaEntry(Document): return self.db.MediaComment.find({ 'media_entry': self['_id']}).sort('created', DESCENDING) - def get_display_media(self, media_map, fetch_order=DISPLAY_IMAGE_FETCHING_ORDER): + def get_display_media(self, media_map, + fetch_order=DISPLAY_IMAGE_FETCHING_ORDER): """ Find the best media for display. @@ -273,7 +275,7 @@ class MediaEntry(Document): """ Provide a url to the previous entry from this user, if there is one """ - cursor = self.db.MediaEntry.find({'_id' : {"$gt": self['_id']}, + cursor = self.db.MediaEntry.find({'_id': {"$gt": self['_id']}, 'uploader': self['uploader'], 'state': 'processed'}).sort( '_id', ASCENDING).limit(1) @@ -286,7 +288,7 @@ class MediaEntry(Document): """ Provide a url to the next entry from this user, if there is one """ - cursor = self.db.MediaEntry.find({'_id' : {"$lt": self['_id']}, + cursor = self.db.MediaEntry.find({'_id': {"$lt": self['_id']}, 'uploader': self['uploader'], 'state': 'processed'}).sort( '_id', DESCENDING).limit(1) @@ -353,4 +355,3 @@ def register_models(connection): Register all models in REGISTER_MODELS with this connection. """ connection.register(REGISTER_MODELS) - -- cgit v1.2.3 From 7cbddc96a85410c14583b598312e40efe6051a44 Mon Sep 17 00:00:00 2001 From: Elrond Date: Mon, 14 Nov 2011 14:21:06 +0100 Subject: Enable mongokit's "Dot notation" mongokit documents can allow to use x.FIELD instead of x["FIELD"]. First it looks a lot more pythonic. Second it might allow us an easier migration path towards an sqlalchemy database backend. Docs: http://namlook.github.com/mongokit/tutorial.html#dot-notation --- mediagoblin/db/models.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index c010cb89..65c15917 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -63,6 +63,7 @@ class User(Document): - bio_html: biography of the user converted to proper HTML. """ __collection__ = 'users' + use_dot_notation = True structure = { 'username': unicode, @@ -177,6 +178,7 @@ class MediaEntry(Document): - fail_metadata: """ __collection__ = 'media_entries' + use_dot_notation = True structure = { 'uploader': ObjectId, @@ -321,6 +323,7 @@ class MediaComment(Document): """ __collection__ = 'media_comments' + use_dot_notation = True structure = { 'media_entry': ObjectId, -- cgit v1.2.3 From eabe6b678a98fd06d9cd8463935a3b842f41485c Mon Sep 17 00:00:00 2001 From: Elrond Date: Sun, 13 Nov 2011 19:25:06 +0100 Subject: Dot-Notation for "_id" Note: Migrations can't use "Dot Notation"! Migrations run on pymongo, not mongokit. So they can't use the "Dot Notation". This isn't really a big issue, as migrations are anyway quite mongo specific. --- mediagoblin/db/models.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 65c15917..1c1bc2fd 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -219,7 +219,7 @@ class MediaEntry(Document): def get_comments(self): return self.db.MediaComment.find({ - 'media_entry': self['_id']}).sort('created', DESCENDING) + 'media_entry': self._id}).sort('created', DESCENDING) def get_display_media(self, media_map, fetch_order=common.DISPLAY_IMAGE_FETCHING_ORDER): @@ -250,7 +250,7 @@ class MediaEntry(Document): {'slug': self['slug']}) if duplicate: - self['slug'] = "%s-%s" % (self['_id'], self['slug']) + self['slug'] = "%s-%s" % (self._id, self['slug']) def url_for_self(self, urlgen): """ @@ -269,13 +269,13 @@ class MediaEntry(Document): return urlgen( 'mediagoblin.user_pages.media_home', user=uploader['username'], - media=unicode(self['_id'])) + media=unicode(self._id)) def url_to_prev(self, urlgen): """ Provide a url to the previous entry from this user, if there is one """ - cursor = self.db.MediaEntry.find({'_id': {"$gt": self['_id']}, + cursor = self.db.MediaEntry.find({'_id': {"$gt": self._id}, 'uploader': self['uploader'], 'state': 'processed'}).sort( '_id', ASCENDING).limit(1) @@ -288,7 +288,7 @@ class MediaEntry(Document): """ Provide a url to the next entry from this user, if there is one """ - cursor = self.db.MediaEntry.find({'_id': {"$lt": self['_id']}, + cursor = self.db.MediaEntry.find({'_id': {"$lt": self._id}, 'uploader': self['uploader'], 'state': 'processed'}).sort( '_id', DESCENDING).limit(1) -- cgit v1.2.3 From e62fc61194508ed5233cdc78e90747450343616f Mon Sep 17 00:00:00 2001 From: "Pablo J. Urbano Santos" Date: Sat, 19 Nov 2011 19:11:42 +0100 Subject: Added parameter ascending to MediaEntry::get_comments, if true, comments will be ordered ascending, otherwise descending --- mediagoblin/db/models.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 1c1bc2fd..f13a4457 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -217,9 +217,14 @@ class MediaEntry(Document): 'created': datetime.datetime.utcnow, 'state': u'unprocessed'} - def get_comments(self): + def get_comments(self, ascending=False): + if ascending: + order = ASCENDING + else: + order = DESCENDING + return self.db.MediaComment.find({ - 'media_entry': self._id}).sort('created', DESCENDING) + 'media_entry': self._id}).sort('created', order) def get_display_media(self, media_map, fetch_order=common.DISPLAY_IMAGE_FETCHING_ORDER): -- cgit v1.2.3 From 30188321531e1b0d3c78166498702bbd8c7dc2bc Mon Sep 17 00:00:00 2001 From: Elrond Date: Mon, 21 Nov 2011 21:40:48 +0100 Subject: Rename MediaEntry.uploader() to .get_uploader() The .uploader() method conflicts with the uploader database field. As we're moving to .FIELD for db field access, this is a relevant conflict. So renaming .uploader() to .get_uploader() --- mediagoblin/db/models.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index f13a4457..265fe36d 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -263,7 +263,7 @@ class MediaEntry(Document): Use a slug if we have one, else use our '_id'. """ - uploader = self.uploader() + uploader = self.get_uploader() if self.get('slug'): return urlgen( @@ -286,7 +286,7 @@ class MediaEntry(Document): '_id', ASCENDING).limit(1) if cursor.count(): return urlgen('mediagoblin.user_pages.media_home', - user=self.uploader()['username'], + user=self.get_uploader()['username'], media=unicode(cursor[0]['slug'])) def url_to_next(self, urlgen): @@ -300,10 +300,10 @@ class MediaEntry(Document): if cursor.count(): return urlgen('mediagoblin.user_pages.media_home', - user=self.uploader()['username'], + user=self.get_uploader()['username'], media=unicode(cursor[0]['slug'])) - def uploader(self): + def get_uploader(self): return self.db.User.find_one({'_id': self['uploader']}) def get_fail_exception(self): -- cgit v1.2.3 From 5a4e3ff1e2a0f2ed451bc191c1d44bcd694b8e75 Mon Sep 17 00:00:00 2001 From: Elrond Date: Mon, 14 Nov 2011 15:39:57 +0100 Subject: Dot-Notation for Users.username --- mediagoblin/db/models.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 265fe36d..4af996b8 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -268,12 +268,12 @@ class MediaEntry(Document): if self.get('slug'): return urlgen( 'mediagoblin.user_pages.media_home', - user=uploader['username'], + user=uploader.username, media=self['slug']) else: return urlgen( 'mediagoblin.user_pages.media_home', - user=uploader['username'], + user=uploader.username, media=unicode(self._id)) def url_to_prev(self, urlgen): @@ -286,7 +286,7 @@ class MediaEntry(Document): '_id', ASCENDING).limit(1) if cursor.count(): return urlgen('mediagoblin.user_pages.media_home', - user=self.get_uploader()['username'], + user=self.get_uploader().username, media=unicode(cursor[0]['slug'])) def url_to_next(self, urlgen): @@ -300,7 +300,7 @@ class MediaEntry(Document): if cursor.count(): return urlgen('mediagoblin.user_pages.media_home', - user=self.get_uploader()['username'], + user=self.get_uploader().username, media=unicode(cursor[0]['slug'])) def get_uploader(self): -- cgit v1.2.3 From 9047b254f340c16f33183f0d6d68e6c4a5a3c8de Mon Sep 17 00:00:00 2001 From: Elrond Date: Mon, 14 Nov 2011 18:49:21 +0100 Subject: Dot-Notation for Users.pw_hash --- mediagoblin/db/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 4af996b8..795cba6a 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -96,7 +96,7 @@ class User(Document): See if a user can login with this password """ return auth_lib.bcrypt_check_password( - password, self['pw_hash']) + password, self.pw_hash) class MediaEntry(Document): -- cgit v1.2.3 From 1ceb4fc8682dd00c15376b75a3d9222cac6fb5bd Mon Sep 17 00:00:00 2001 From: Elrond Date: Mon, 21 Nov 2011 20:18:38 +0100 Subject: Dot-Notation for MediaEntry.uploader --- mediagoblin/db/models.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 795cba6a..f1f56dd1 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -281,7 +281,7 @@ class MediaEntry(Document): Provide a url to the previous entry from this user, if there is one """ cursor = self.db.MediaEntry.find({'_id': {"$gt": self._id}, - 'uploader': self['uploader'], + 'uploader': self.uploader, 'state': 'processed'}).sort( '_id', ASCENDING).limit(1) if cursor.count(): @@ -294,7 +294,7 @@ class MediaEntry(Document): Provide a url to the next entry from this user, if there is one """ cursor = self.db.MediaEntry.find({'_id': {"$lt": self._id}, - 'uploader': self['uploader'], + 'uploader': self.uploader, 'state': 'processed'}).sort( '_id', DESCENDING).limit(1) @@ -304,7 +304,7 @@ class MediaEntry(Document): media=unicode(cursor[0]['slug'])) def get_uploader(self): - return self.db.User.find_one({'_id': self['uploader']}) + return self.db.User.find_one({'_id': self.uploader}) def get_fail_exception(self): """ -- cgit v1.2.3 From ec82fbd85c93c88d177e9f5c7a7414a2b47fa17e Mon Sep 17 00:00:00 2001 From: Elrond Date: Wed, 23 Nov 2011 00:10:42 +0100 Subject: Dot-Notation for MediaEntry.title --- mediagoblin/db/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index f1f56dd1..7af76b9f 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -249,7 +249,7 @@ class MediaEntry(Document): pass def generate_slug(self): - self['slug'] = url.slugify(self['title']) + self['slug'] = url.slugify(self.title) duplicate = mg_globals.database.media_entries.find_one( {'slug': self['slug']}) -- cgit v1.2.3 From 5da0bf901be7551e9708dd248319ff57d7b29a57 Mon Sep 17 00:00:00 2001 From: Elrond Date: Sun, 4 Dec 2011 19:57:42 +0100 Subject: Dot-Notation for MediaEntry.slug --- mediagoblin/db/models.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 7af76b9f..aeee69dd 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -249,13 +249,13 @@ class MediaEntry(Document): pass def generate_slug(self): - self['slug'] = url.slugify(self.title) + self.slug = url.slugify(self.title) duplicate = mg_globals.database.media_entries.find_one( - {'slug': self['slug']}) + {'slug': self.slug}) if duplicate: - self['slug'] = "%s-%s" % (self._id, self['slug']) + self.slug = "%s-%s" % (self._id, self.slug) def url_for_self(self, urlgen): """ @@ -269,7 +269,7 @@ class MediaEntry(Document): return urlgen( 'mediagoblin.user_pages.media_home', user=uploader.username, - media=self['slug']) + media=self.slug) else: return urlgen( 'mediagoblin.user_pages.media_home', @@ -287,7 +287,7 @@ class MediaEntry(Document): if cursor.count(): return urlgen('mediagoblin.user_pages.media_home', user=self.get_uploader().username, - media=unicode(cursor[0]['slug'])) + media=unicode(cursor[0].slug)) def url_to_next(self, urlgen): """ @@ -301,7 +301,7 @@ class MediaEntry(Document): if cursor.count(): return urlgen('mediagoblin.user_pages.media_home', user=self.get_uploader().username, - media=unicode(cursor[0]['slug'])) + media=unicode(cursor[0].slug)) def get_uploader(self): return self.db.User.find_one({'_id': self.uploader}) -- cgit v1.2.3 From ddc1cae9ea4c80415557ec0408a56a3a1c60423b Mon Sep 17 00:00:00 2001 From: Elrond Date: Sun, 4 Dec 2011 20:26:36 +0100 Subject: Dot-Notation for MediaEntry.media_data --- mediagoblin/db/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index aeee69dd..569c3600 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -131,7 +131,7 @@ class MediaEntry(Document): For example, images might contain some EXIF data that's not appropriate to other formats. You might store it like: - mediaentry['media_data']['exif'] = { + mediaentry.media_data['exif'] = { 'manufacturer': 'CASIO', 'model': 'QV-4000', 'exposure_time': .659} @@ -139,7 +139,7 @@ class MediaEntry(Document): Alternately for video you might store: # play length in seconds - mediaentry['media_data']['play_length'] = 340 + mediaentry.media_data['play_length'] = 340 ... so what's appropriate here really depends on the media type. -- cgit v1.2.3 From 59bd06aabb0b9a6277d2ad5d1d38c3c8a8da5298 Mon Sep 17 00:00:00 2001 From: Elrond Date: Tue, 20 Dec 2011 19:35:47 +0100 Subject: Move db/util.py -> db/mongo/util.py - Change some reference - Provide a wrapper db/util.py --- mediagoblin/db/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 569c3600..51c6e98e 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -22,7 +22,7 @@ from mongokit import Document from mediagoblin.auth import lib as auth_lib from mediagoblin import mg_globals from mediagoblin.db import migrations -from mediagoblin.db.util import ASCENDING, DESCENDING, ObjectId +from mediagoblin.db.mongo.util import ASCENDING, DESCENDING, ObjectId from mediagoblin.tools.pagination import Pagination from mediagoblin.tools import url, common -- cgit v1.2.3 From faf74067dae0f6f9d200a30369e9b7a4501b66ab Mon Sep 17 00:00:00 2001 From: Elrond Date: Tue, 20 Dec 2011 20:33:33 +0100 Subject: Move db/migrations.py -> db/mongo/migrations.py And change references. --- mediagoblin/db/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 51c6e98e..e2ac1b5a 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -21,7 +21,7 @@ from mongokit import Document from mediagoblin.auth import lib as auth_lib from mediagoblin import mg_globals -from mediagoblin.db import migrations +from mediagoblin.db.mongo import migrations from mediagoblin.db.mongo.util import ASCENDING, DESCENDING, ObjectId from mediagoblin.tools.pagination import Pagination from mediagoblin.tools import url, common -- cgit v1.2.3 From 4ae4012dad3f5638ea7b510d40f0b4d0b641fe2a Mon Sep 17 00:00:00 2001 From: Elrond Date: Tue, 20 Dec 2011 20:41:21 +0100 Subject: Move db/models.py -> db/mongo/models.py To my surprise, there was only ONE reference to models.py. From open.py. --- mediagoblin/db/models.py | 363 ----------------------------------------------- 1 file changed, 363 deletions(-) delete mode 100644 mediagoblin/db/models.py (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py deleted file mode 100644 index e2ac1b5a..00000000 --- a/mediagoblin/db/models.py +++ /dev/null @@ -1,363 +0,0 @@ -# GNU MediaGoblin -- federated, autonomous media hosting -# Copyright (C) 2011 MediaGoblin contributors. See AUTHORS. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . - -import datetime -import uuid - -from mongokit import Document - -from mediagoblin.auth import lib as auth_lib -from mediagoblin import mg_globals -from mediagoblin.db.mongo import migrations -from mediagoblin.db.mongo.util import ASCENDING, DESCENDING, ObjectId -from mediagoblin.tools.pagination import Pagination -from mediagoblin.tools import url, common - -################### -# Custom validators -################### - -######## -# Models -######## - - -class User(Document): - """ - A user of MediaGoblin. - - Structure: - - username: The username of this user, should be unique to this instance. - - email: Email address of this user - - created: When the user was created - - plugin_data: a mapping of extra plugin information for this User. - Nothing uses this yet as we don't have plugins, but someday we - might... :) - - pw_hash: Hashed version of user's password. - - email_verified: Whether or not the user has verified their email or not. - Most parts of the site are disabled for users who haven't yet. - - status: whether or not the user is active, etc. Currently only has two - values, 'needs_email_verification' or 'active'. (In the future, maybe - we'll change this to a boolean with a key of 'active' and have a - separate field for a reason the user's been disabled if that's - appropriate... email_verified is already separate, after all.) - - verification_key: If the user is awaiting email verification, the user - will have to provide this key (which will be encoded in the presented - URL) in order to confirm their email as active. - - is_admin: Whether or not this user is an administrator or not. - - url: this user's personal webpage/website, if appropriate. - - bio: biography of this user (plaintext, in markdown) - - bio_html: biography of the user converted to proper HTML. - """ - __collection__ = 'users' - use_dot_notation = True - - structure = { - 'username': unicode, - 'email': unicode, - 'created': datetime.datetime, - 'plugin_data': dict, # plugins can dump stuff here. - 'pw_hash': unicode, - 'email_verified': bool, - 'status': unicode, - 'verification_key': unicode, - 'is_admin': bool, - 'url': unicode, - 'bio': unicode, # May contain markdown - 'bio_html': unicode, # May contain plaintext, or HTML - 'fp_verification_key': unicode, # forgotten password verification key - 'fp_token_expire': datetime.datetime, - } - - required_fields = ['username', 'created', 'pw_hash', 'email'] - - default_values = { - 'created': datetime.datetime.utcnow, - 'email_verified': False, - 'status': u'needs_email_verification', - 'verification_key': lambda: unicode(uuid.uuid4()), - 'is_admin': False} - - def check_login(self, password): - """ - See if a user can login with this password - """ - return auth_lib.bcrypt_check_password( - password, self.pw_hash) - - -class MediaEntry(Document): - """ - Record of a piece of media. - - Structure: - - uploader: A reference to a User who uploaded this. - - - title: Title of this work - - - slug: A normalized "slug" which can be used as part of a URL to retrieve - this work, such as 'my-works-name-in-slug-form' may be viewable by - 'http://mg.example.org/u/username/m/my-works-name-in-slug-form/' - Note that since URLs are constructed this way, slugs must be unique - per-uploader. (An index is provided to enforce that but code should be - written on the python side to ensure this as well.) - - - created: Date and time of when this piece of work was uploaded. - - - description: Uploader-set description of this work. This can be marked - up with MarkDown for slight fanciness (links, boldness, italics, - paragraphs...) - - - description_html: Rendered version of the description, run through - Markdown and cleaned with our cleaning tool. - - - media_type: What type of media is this? Currently we only support - 'image' ;) - - - media_data: Extra information that's media-format-dependent. - For example, images might contain some EXIF data that's not appropriate - to other formats. You might store it like: - - mediaentry.media_data['exif'] = { - 'manufacturer': 'CASIO', - 'model': 'QV-4000', - 'exposure_time': .659} - - Alternately for video you might store: - - # play length in seconds - mediaentry.media_data['play_length'] = 340 - - ... so what's appropriate here really depends on the media type. - - - plugin_data: a mapping of extra plugin information for this User. - Nothing uses this yet as we don't have plugins, but someday we - might... :) - - - tags: A list of tags. Each tag is stored as a dictionary that has a key - for the actual name and the normalized name-as-slug, so ultimately this - looks like: - [{'name': 'Gully Gardens', - 'slug': 'gully-gardens'}, - {'name': 'Castle Adventure Time?!", - 'slug': 'castle-adventure-time'}] - - - state: What's the state of this file? Active, inactive, disabled, etc... - But really for now there are only two states: - "unprocessed": uploaded but needs to go through processing for display - "processed": processed and able to be displayed - - - queued_media_file: storage interface style filepath describing a file - queued for processing. This is stored in the mg_globals.queue_store - storage system. - - - queued_task_id: celery task id. Use this to fetch the task state. - - - media_files: Files relevant to this that have actually been processed - and are available for various types of display. Stored like: - {'thumb': ['dir1', 'dir2', 'pic.png'} - - - attachment_files: A list of "attachment" files, ones that aren't - critical to this piece of media but may be usefully relevant to people - viewing the work. (currently unused.) - - - fail_error: path to the exception raised - - fail_metadata: - """ - __collection__ = 'media_entries' - use_dot_notation = True - - structure = { - 'uploader': ObjectId, - 'title': unicode, - 'slug': unicode, - 'created': datetime.datetime, - 'description': unicode, # May contain markdown/up - 'description_html': unicode, # May contain plaintext, or HTML - 'media_type': unicode, - 'media_data': dict, # extra data relevant to this media_type - 'plugin_data': dict, # plugins can dump stuff here. - 'tags': [dict], - 'state': unicode, - - # For now let's assume there can only be one main file queued - # at a time - 'queued_media_file': [unicode], - 'queued_task_id': unicode, - - # A dictionary of logical names to filepaths - 'media_files': dict, - - # The following should be lists of lists, in appropriate file - # record form - 'attachment_files': list, - - # If things go badly in processing things, we'll store that - # data here - 'fail_error': unicode, - 'fail_metadata': dict} - - required_fields = [ - 'uploader', 'created', 'media_type', 'slug'] - - default_values = { - 'created': datetime.datetime.utcnow, - 'state': u'unprocessed'} - - def get_comments(self, ascending=False): - if ascending: - order = ASCENDING - else: - order = DESCENDING - - return self.db.MediaComment.find({ - 'media_entry': self._id}).sort('created', order) - - def get_display_media(self, media_map, - fetch_order=common.DISPLAY_IMAGE_FETCHING_ORDER): - """ - Find the best media for display. - - Args: - - media_map: a dict like - {u'image_size': [u'dir1', u'dir2', u'image.jpg']} - - fetch_order: the order we should try fetching images in - - Returns: - (media_size, media_path) - """ - media_sizes = media_map.keys() - - for media_size in common.DISPLAY_IMAGE_FETCHING_ORDER: - if media_size in media_sizes: - return media_map[media_size] - - def main_mediafile(self): - pass - - def generate_slug(self): - self.slug = url.slugify(self.title) - - duplicate = mg_globals.database.media_entries.find_one( - {'slug': self.slug}) - - if duplicate: - self.slug = "%s-%s" % (self._id, self.slug) - - def url_for_self(self, urlgen): - """ - Generate an appropriate url for ourselves - - Use a slug if we have one, else use our '_id'. - """ - uploader = self.get_uploader() - - if self.get('slug'): - return urlgen( - 'mediagoblin.user_pages.media_home', - user=uploader.username, - media=self.slug) - else: - return urlgen( - 'mediagoblin.user_pages.media_home', - user=uploader.username, - media=unicode(self._id)) - - def url_to_prev(self, urlgen): - """ - Provide a url to the previous entry from this user, if there is one - """ - cursor = self.db.MediaEntry.find({'_id': {"$gt": self._id}, - 'uploader': self.uploader, - 'state': 'processed'}).sort( - '_id', ASCENDING).limit(1) - if cursor.count(): - return urlgen('mediagoblin.user_pages.media_home', - user=self.get_uploader().username, - media=unicode(cursor[0].slug)) - - def url_to_next(self, urlgen): - """ - Provide a url to the next entry from this user, if there is one - """ - cursor = self.db.MediaEntry.find({'_id': {"$lt": self._id}, - 'uploader': self.uploader, - 'state': 'processed'}).sort( - '_id', DESCENDING).limit(1) - - if cursor.count(): - return urlgen('mediagoblin.user_pages.media_home', - user=self.get_uploader().username, - media=unicode(cursor[0].slug)) - - def get_uploader(self): - return self.db.User.find_one({'_id': self.uploader}) - - def get_fail_exception(self): - """ - Get the exception that's appropriate for this error - """ - if self['fail_error']: - return common.import_component(self['fail_error']) - - -class MediaComment(Document): - """ - A comment on a MediaEntry. - - Structure: - - media_entry: The media entry this comment is attached to - - author: user who posted this comment - - created: when the comment was created - - content: plaintext (but markdown'able) version of the comment's content. - - content_html: the actual html-rendered version of the comment displayed. - Run through Markdown and the HTML cleaner. - """ - - __collection__ = 'media_comments' - use_dot_notation = True - - structure = { - 'media_entry': ObjectId, - 'author': ObjectId, - 'created': datetime.datetime, - 'content': unicode, - 'content_html': unicode} - - required_fields = [ - 'media_entry', 'author', 'created', 'content'] - - default_values = { - 'created': datetime.datetime.utcnow} - - def media_entry(self): - return self.db.MediaEntry.find_one({'_id': self['media_entry']}) - - def author(self): - return self.db.User.find_one({'_id': self['author']}) - - -REGISTER_MODELS = [ - MediaEntry, - User, - MediaComment] - - -def register_models(connection): - """ - Register all models in REGISTER_MODELS with this connection. - """ - connection.register(REGISTER_MODELS) -- cgit v1.2.3 From b0c8328e547288028e7e43f0ceb1fa9f7c8dac4a Mon Sep 17 00:00:00 2001 From: Sebastian Spaeth Date: Fri, 30 Nov 2012 10:10:35 +0100 Subject: Move db.sql.models* to db.models* --- mediagoblin/db/models.py | 447 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 447 insertions(+) create mode 100644 mediagoblin/db/models.py (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py new file mode 100644 index 00000000..4450e38d --- /dev/null +++ b/mediagoblin/db/models.py @@ -0,0 +1,447 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +""" +TODO: indexes on foreignkeys, where useful. +""" + + +import datetime +import sys + +from sqlalchemy import Column, Integer, Unicode, UnicodeText, DateTime, \ + Boolean, ForeignKey, UniqueConstraint, PrimaryKeyConstraint, \ + SmallInteger +from sqlalchemy.orm import relationship, backref +from sqlalchemy.orm.collections import attribute_mapped_collection +from sqlalchemy.sql.expression import desc +from sqlalchemy.ext.associationproxy import association_proxy +from sqlalchemy.util import memoized_property + +from mediagoblin.db.sql.extratypes import PathTupleWithSlashes, JSONEncoded +from mediagoblin.db.sql.base import Base, DictReadAttrProxy +from mediagoblin.db.mixin import UserMixin, MediaEntryMixin, MediaCommentMixin, CollectionMixin, CollectionItemMixin +from mediagoblin.db.sql.base import Session + +# It's actually kind of annoying how sqlalchemy-migrate does this, if +# I understand it right, but whatever. Anyway, don't remove this :P +# +# We could do migration calls more manually instead of relying on +# this import-based meddling... +from migrate import changeset + + +class User(Base, UserMixin): + """ + TODO: We should consider moving some rarely used fields + into some sort of "shadow" table. + """ + __tablename__ = "core__users" + + id = Column(Integer, primary_key=True) + username = Column(Unicode, nullable=False, unique=True) + email = Column(Unicode, nullable=False) + created = Column(DateTime, nullable=False, default=datetime.datetime.now) + pw_hash = Column(Unicode, nullable=False) + email_verified = Column(Boolean, default=False) + status = Column(Unicode, default=u"needs_email_verification", nullable=False) + # Intented to be nullable=False, but migrations would not work for it + # set to nullable=True implicitly. + wants_comment_notification = Column(Boolean, default=True) + verification_key = Column(Unicode) + is_admin = Column(Boolean, default=False, nullable=False) + url = Column(Unicode) + bio = Column(UnicodeText) # ?? + fp_verification_key = Column(Unicode) + fp_token_expire = Column(DateTime) + + ## TODO + # plugin data would be in a separate model + + def __repr__(self): + return '<{0} #{1} {2} {3} "{4}">'.format( + self.__class__.__name__, + self.id, + 'verified' if self.email_verified else 'non-verified', + 'admin' if self.is_admin else 'user', + self.username) + + +class MediaEntry(Base, MediaEntryMixin): + """ + TODO: Consider fetching the media_files using join + """ + __tablename__ = "core__media_entries" + + id = Column(Integer, primary_key=True) + uploader = Column(Integer, ForeignKey(User.id), nullable=False, index=True) + title = Column(Unicode, nullable=False) + slug = Column(Unicode) + created = Column(DateTime, nullable=False, default=datetime.datetime.now, + index=True) + description = Column(UnicodeText) # ?? + media_type = Column(Unicode, nullable=False) + state = Column(Unicode, default=u'unprocessed', nullable=False) + # or use sqlalchemy.types.Enum? + license = Column(Unicode) + collected = Column(Integer, default=0) + + fail_error = Column(Unicode) + fail_metadata = Column(JSONEncoded) + + transcoding_progress = Column(SmallInteger) + + queued_media_file = Column(PathTupleWithSlashes) + + queued_task_id = Column(Unicode) + + __table_args__ = ( + UniqueConstraint('uploader', 'slug'), + {}) + + get_uploader = relationship(User) + + media_files_helper = relationship("MediaFile", + collection_class=attribute_mapped_collection("name"), + cascade="all, delete-orphan" + ) + media_files = association_proxy('media_files_helper', 'file_path', + creator=lambda k, v: MediaFile(name=k, file_path=v) + ) + + attachment_files_helper = relationship("MediaAttachmentFile", + cascade="all, delete-orphan", + order_by="MediaAttachmentFile.created" + ) + attachment_files = association_proxy("attachment_files_helper", "dict_view", + creator=lambda v: MediaAttachmentFile( + name=v["name"], filepath=v["filepath"]) + ) + + tags_helper = relationship("MediaTag", + cascade="all, delete-orphan" + ) + tags = association_proxy("tags_helper", "dict_view", + creator=lambda v: MediaTag(name=v["name"], slug=v["slug"]) + ) + + collections_helper = relationship("CollectionItem", + cascade="all, delete-orphan" + ) + collections = association_proxy("collections_helper", "in_collection") + + ## TODO + # media_data + # fail_error + + def get_comments(self, ascending=False): + order_col = MediaComment.created + if not ascending: + order_col = desc(order_col) + return MediaComment.query.filter_by( + media_entry=self.id).order_by(order_col) + + def url_to_prev(self, urlgen): + """get the next 'newer' entry by this user""" + media = MediaEntry.query.filter( + (MediaEntry.uploader == self.uploader) + & (MediaEntry.state == u'processed') + & (MediaEntry.id > self.id)).order_by(MediaEntry.id).first() + + if media is not None: + return media.url_for_self(urlgen) + + def url_to_next(self, urlgen): + """get the next 'older' entry by this user""" + media = MediaEntry.query.filter( + (MediaEntry.uploader == self.uploader) + & (MediaEntry.state == u'processed') + & (MediaEntry.id < self.id)).order_by(desc(MediaEntry.id)).first() + + if media is not None: + return media.url_for_self(urlgen) + + #@memoized_property + @property + def media_data(self): + session = Session() + + return session.query(self.media_data_table).filter_by( + media_entry=self.id).first() + + def media_data_init(self, **kwargs): + """ + Initialize or update the contents of a media entry's media_data row + """ + session = Session() + + media_data = session.query(self.media_data_table).filter_by( + media_entry=self.id).first() + + # No media data, so actually add a new one + if media_data is None: + media_data = self.media_data_table( + media_entry=self.id, + **kwargs) + session.add(media_data) + # Update old media data + else: + for field, value in kwargs.iteritems(): + setattr(media_data, field, value) + + @memoized_property + def media_data_table(self): + # TODO: memoize this + models_module = self.media_type + '.models' + __import__(models_module) + return sys.modules[models_module].DATA_MODEL + + def __repr__(self): + safe_title = self.title.encode('ascii', 'replace') + + return '<{classname} {id}: {title}>'.format( + classname=self.__class__.__name__, + id=self.id, + title=safe_title) + + +class FileKeynames(Base): + """ + keywords for various places. + currently the MediaFile keys + """ + __tablename__ = "core__file_keynames" + id = Column(Integer, primary_key=True) + name = Column(Unicode, unique=True) + + def __repr__(self): + return "" % (self.id, self.name) + + @classmethod + def find_or_new(cls, name): + t = cls.query.filter_by(name=name).first() + if t is not None: + return t + return cls(name=name) + + +class MediaFile(Base): + """ + TODO: Highly consider moving "name" into a new table. + TODO: Consider preloading said table in software + """ + __tablename__ = "core__mediafiles" + + media_entry = Column( + Integer, ForeignKey(MediaEntry.id), + nullable=False) + name_id = Column(SmallInteger, ForeignKey(FileKeynames.id), nullable=False) + file_path = Column(PathTupleWithSlashes) + + __table_args__ = ( + PrimaryKeyConstraint('media_entry', 'name_id'), + {}) + + def __repr__(self): + return "" % (self.name, self.file_path) + + name_helper = relationship(FileKeynames, lazy="joined", innerjoin=True) + name = association_proxy('name_helper', 'name', + creator=FileKeynames.find_or_new + ) + + +class MediaAttachmentFile(Base): + __tablename__ = "core__attachment_files" + + id = Column(Integer, primary_key=True) + media_entry = Column( + Integer, ForeignKey(MediaEntry.id), + nullable=False) + name = Column(Unicode, nullable=False) + filepath = Column(PathTupleWithSlashes) + created = Column(DateTime, nullable=False, default=datetime.datetime.now) + + @property + def dict_view(self): + """A dict like view on this object""" + return DictReadAttrProxy(self) + + +class Tag(Base): + __tablename__ = "core__tags" + + id = Column(Integer, primary_key=True) + slug = Column(Unicode, nullable=False, unique=True) + + def __repr__(self): + return "" % (self.id, self.slug) + + @classmethod + def find_or_new(cls, slug): + t = cls.query.filter_by(slug=slug).first() + if t is not None: + return t + return cls(slug=slug) + + +class MediaTag(Base): + __tablename__ = "core__media_tags" + + id = Column(Integer, primary_key=True) + media_entry = Column( + Integer, ForeignKey(MediaEntry.id), + nullable=False, index=True) + tag = Column(Integer, ForeignKey(Tag.id), nullable=False, index=True) + name = Column(Unicode) + # created = Column(DateTime, nullable=False, default=datetime.datetime.now) + + __table_args__ = ( + UniqueConstraint('tag', 'media_entry'), + {}) + + tag_helper = relationship(Tag) + slug = association_proxy('tag_helper', 'slug', + creator=Tag.find_or_new + ) + + def __init__(self, name=None, slug=None): + Base.__init__(self) + if name is not None: + self.name = name + if slug is not None: + self.tag_helper = Tag.find_or_new(slug) + + @property + def dict_view(self): + """A dict like view on this object""" + return DictReadAttrProxy(self) + + +class MediaComment(Base, MediaCommentMixin): + __tablename__ = "core__media_comments" + + id = Column(Integer, primary_key=True) + media_entry = Column( + Integer, ForeignKey(MediaEntry.id), nullable=False, index=True) + author = Column(Integer, ForeignKey(User.id), nullable=False) + created = Column(DateTime, nullable=False, default=datetime.datetime.now) + content = Column(UnicodeText, nullable=False) + + get_author = relationship(User) + + +class Collection(Base, CollectionMixin): + __tablename__ = "core__collections" + + id = Column(Integer, primary_key=True) + title = Column(Unicode, nullable=False) + slug = Column(Unicode) + created = Column(DateTime, nullable=False, default=datetime.datetime.now, + index=True) + description = Column(UnicodeText) + creator = Column(Integer, ForeignKey(User.id), nullable=False) + items = Column(Integer, default=0) + + get_creator = relationship(User) + + def get_collection_items(self, ascending=False): + order_col = CollectionItem.position + if not ascending: + order_col = desc(order_col) + return CollectionItem.query.filter_by( + collection=self.id).order_by(order_col) + + +class CollectionItem(Base, CollectionItemMixin): + __tablename__ = "core__collection_items" + + id = Column(Integer, primary_key=True) + media_entry = Column( + Integer, ForeignKey(MediaEntry.id), nullable=False, index=True) + collection = Column(Integer, ForeignKey(Collection.id), nullable=False) + note = Column(UnicodeText, nullable=True) + added = Column(DateTime, nullable=False, default=datetime.datetime.now) + position = Column(Integer) + in_collection = relationship("Collection") + + get_media_entry = relationship(MediaEntry) + + __table_args__ = ( + UniqueConstraint('collection', 'media_entry'), + {}) + + @property + def dict_view(self): + """A dict like view on this object""" + return DictReadAttrProxy(self) + + +class ProcessingMetaData(Base): + __tablename__ = 'core__processing_metadata' + + id = Column(Integer, primary_key=True) + media_entry_id = Column(Integer, ForeignKey(MediaEntry.id), nullable=False, + index=True) + media_entry = relationship(MediaEntry, + backref=backref('processing_metadata', + cascade='all, delete-orphan')) + callback_url = Column(Unicode) + + @property + def dict_view(self): + """A dict like view on this object""" + return DictReadAttrProxy(self) + + +MODELS = [ + User, MediaEntry, Tag, MediaTag, MediaComment, Collection, CollectionItem, MediaFile, FileKeynames, + MediaAttachmentFile, ProcessingMetaData] + + +###################################################### +# Special, migrations-tracking table +# +# Not listed in MODELS because this is special and not +# really migrated, but used for migrations (for now) +###################################################### + +class MigrationData(Base): + __tablename__ = "core__migrations" + + name = Column(Unicode, primary_key=True) + version = Column(Integer, nullable=False, default=0) + +###################################################### + + +def show_table_init(engine_uri): + if engine_uri is None: + engine_uri = 'sqlite:///:memory:' + from sqlalchemy import create_engine + engine = create_engine(engine_uri, echo=True) + + Base.metadata.create_all(engine) + + +if __name__ == '__main__': + from sys import argv + print repr(argv) + if len(argv) == 2: + uri = argv[1] + else: + uri = None + show_table_init(uri) -- cgit v1.2.3 From a5acfe23fadc4772fdd6fd63091c32621b94d710 Mon Sep 17 00:00:00 2001 From: Sebastian Spaeth Date: Mon, 7 Jan 2013 13:03:33 +0100 Subject: Move mediagoblin.db.sql.extratypes to mediagoblin.db.extratypes No other functional changes. --- mediagoblin/db/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 4450e38d..54f9abbc 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -31,7 +31,7 @@ from sqlalchemy.sql.expression import desc from sqlalchemy.ext.associationproxy import association_proxy from sqlalchemy.util import memoized_property -from mediagoblin.db.sql.extratypes import PathTupleWithSlashes, JSONEncoded +from mediagoblin.db.extratypes import PathTupleWithSlashes, JSONEncoded from mediagoblin.db.sql.base import Base, DictReadAttrProxy from mediagoblin.db.mixin import UserMixin, MediaEntryMixin, MediaCommentMixin, CollectionMixin, CollectionItemMixin from mediagoblin.db.sql.base import Session -- cgit v1.2.3 From 39dc3bf8db4a0a21220560a259574da4f2c1e12a Mon Sep 17 00:00:00 2001 From: Sebastian Spaeth Date: Mon, 7 Jan 2013 13:03:51 +0100 Subject: Mv db.sql.base to db.base This concludes the db.sql.* -> db.* move. Our db abstraction layer is sqlalchemy, so there is no need to a separate db.sql.* hierarchy. All tests have been run for each of the commit series to make sure everything works at every step. --- mediagoblin/db/models.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 54f9abbc..ea915ae5 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -32,9 +32,8 @@ from sqlalchemy.ext.associationproxy import association_proxy from sqlalchemy.util import memoized_property from mediagoblin.db.extratypes import PathTupleWithSlashes, JSONEncoded -from mediagoblin.db.sql.base import Base, DictReadAttrProxy +from mediagoblin.db.base import Base, DictReadAttrProxy, Session from mediagoblin.db.mixin import UserMixin, MediaEntryMixin, MediaCommentMixin, CollectionMixin, CollectionItemMixin -from mediagoblin.db.sql.base import Session # It's actually kind of annoying how sqlalchemy-migrate does this, if # I understand it right, but whatever. Anyway, don't remove this :P -- cgit v1.2.3 From fdc34b8ba7465b528c7685be3f24c7d7d6d8748b Mon Sep 17 00:00:00 2001 From: Sebastian Spaeth Date: Thu, 15 Nov 2012 11:44:50 +0100 Subject: Implement MediaEntry().delete() (#540) Deleting a MediaEntry instance will automatically delete all related comments and files/attachments. This moves implementation logic out of views.py and allows to make use of this functionality when e.g. deleting a User() account. Whenever a MediaEntry entry is deleted, this will also sql-delete the corresponding MediaFile entry. Signed-off-by: Sebastian Spaeth --- mediagoblin/db/models.py | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index ea915ae5..aeec8aea 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -18,7 +18,7 @@ TODO: indexes on foreignkeys, where useful. """ - +import logging import datetime import sys @@ -34,6 +34,7 @@ from sqlalchemy.util import memoized_property from mediagoblin.db.extratypes import PathTupleWithSlashes, JSONEncoded from mediagoblin.db.base import Base, DictReadAttrProxy, Session from mediagoblin.db.mixin import UserMixin, MediaEntryMixin, MediaCommentMixin, CollectionMixin, CollectionItemMixin +from mediagoblin.tools.files import delete_media_files # It's actually kind of annoying how sqlalchemy-migrate does this, if # I understand it right, but whatever. Anyway, don't remove this :P @@ -42,6 +43,8 @@ from mediagoblin.db.mixin import UserMixin, MediaEntryMixin, MediaCommentMixin, # this import-based meddling... from migrate import changeset +_log = logging.getLogger(__name__) + class User(Base, UserMixin): """ @@ -122,7 +125,6 @@ class MediaEntry(Base, MediaEntryMixin): ) attachment_files_helper = relationship("MediaAttachmentFile", - cascade="all, delete-orphan", order_by="MediaAttachmentFile.created" ) attachment_files = association_proxy("attachment_files_helper", "dict_view", @@ -131,7 +133,7 @@ class MediaEntry(Base, MediaEntryMixin): ) tags_helper = relationship("MediaTag", - cascade="all, delete-orphan" + cascade="all, delete-orphan" # should be automatically deleted ) tags = association_proxy("tags_helper", "dict_view", creator=lambda v: MediaTag(name=v["name"], slug=v["slug"]) @@ -216,6 +218,37 @@ class MediaEntry(Base, MediaEntryMixin): id=self.id, title=safe_title) + def delete(self, del_orphan_tags=True, **kwargs): + """Delete MediaEntry and all related files/attachments/comments + + This will *not* automatically delete unused collections, which + can remain empty... + + :param del_orphan_tags: True/false if we delete unused Tags too + :param commit: True/False if this should end the db transaction""" + # User's CollectionItems are automatically deleted via "cascade". + # Delete all the associated comments + for comment in self.get_comments(): + comment.delete(commit=False) + + # Delete all related files/attachments + try: + delete_media_files(self) + except OSError, error: + # Returns list of files we failed to delete + _log.error('No such files from the user "{1}" to delete: ' + '{0}'.format(str(error), self.get_uploader)) + _log.info('Deleted Media entry id "{0}"'.format(self.id)) + # Related MediaTag's are automatically cleaned, but we might + # want to clean out unused Tag's too. + if del_orphan_tags: + # TODO: Import here due to cyclic imports!!! + # This cries for refactoring + from mediagoblin.db.util import clean_orphan_tags + clean_orphan_tags(commit=False) + # pass through commit=False/True in kwargs + super(MediaEntry, self).delete(**kwargs) + class FileKeynames(Base): """ -- cgit v1.2.3 From 242776e3637e886c510cd39d4d6195932bb973cd Mon Sep 17 00:00:00 2001 From: Sebastian Spaeth Date: Wed, 28 Nov 2012 16:15:46 +0100 Subject: Implement Collection.delete() Deleting a Collection should automatically delete all containing items. Signed-off-by: Sebastian Spaeth --- mediagoblin/db/models.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index aeec8aea..e8616404 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -377,6 +377,10 @@ class MediaComment(Base, MediaCommentMixin): class Collection(Base, CollectionMixin): + """An 'album' or 'set' of media by a user. + + On deletion, contained CollectionItems get automatically reaped via + SQL cascade""" __tablename__ = "core__collections" id = Column(Integer, primary_key=True) @@ -386,11 +390,13 @@ class Collection(Base, CollectionMixin): index=True) description = Column(UnicodeText) creator = Column(Integer, ForeignKey(User.id), nullable=False) + # TODO: No of items in Collection. Badly named, can we migrate to num_items? items = Column(Integer, default=0) get_creator = relationship(User) def get_collection_items(self, ascending=False): + #TODO, is this still needed with self.collection_items being available? order_col = CollectionItem.position if not ascending: order_col = desc(order_col) @@ -408,7 +414,10 @@ class CollectionItem(Base, CollectionItemMixin): note = Column(UnicodeText, nullable=True) added = Column(DateTime, nullable=False, default=datetime.datetime.now) position = Column(Integer) - in_collection = relationship("Collection") + in_collection = relationship("Collection", + backref=backref( + "collection_items", + cascade="all, delete-orphan")) get_media_entry = relationship(MediaEntry) -- cgit v1.2.3 From 03b4fc500cf490fb56b6dba8507d560dffcd2a8b Mon Sep 17 00:00:00 2001 From: Sebastian Spaeth Date: Wed, 28 Nov 2012 15:46:40 +0100 Subject: Implement User.delete() (#540) Set User.collections to her Collections using the backref feature. This way we can iterate a user's collections and delete them all. Delete all MediaEntries/Files/attachments/comments/collections etc before finally deleting the User object. This is the backend work for issue 302 (allow a user to delete ones own account) --- mediagoblin/db/models.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index e8616404..1c6cbda7 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -81,6 +81,27 @@ class User(Base, UserMixin): 'admin' if self.is_admin else 'user', self.username) + def delete(self, **kwargs): + """Deletes a User and all related entries/comments/files/...""" + # Delete this user's Collections and all contained CollectionItems + for collection in self.collections: + collection.delete(commit=False) + + media_entries = MediaEntry.query.filter(MediaEntry.uploader == self.id) + for media in media_entries: + # TODO: Make sure that "MediaEntry.delete()" also deletes + # all related files/Comments + media.delete(del_orphan_tags=False, commit=False) + + # Delete now unused tags + # TODO: import here due to cyclic imports!!! This cries for refactoring + from mediagoblin.db.sql.util import clean_orphan_tags + clean_orphan_tags(commit=False) + + # Delete user, pass through commit=False/True in kwargs + super(User, self).delete(**kwargs) + _log.info('Deleted user "{0}" account'.format(self.username)) + class MediaEntry(Base, MediaEntryMixin): """ @@ -393,7 +414,7 @@ class Collection(Base, CollectionMixin): # TODO: No of items in Collection. Badly named, can we migrate to num_items? items = Column(Integer, default=0) - get_creator = relationship(User) + get_creator = relationship(User, backref="collections") def get_collection_items(self, ascending=False): #TODO, is this still needed with self.collection_items being available? -- cgit v1.2.3 From 3809a8b8e231d7eb22935cf78225121b9043e7fe Mon Sep 17 00:00:00 2001 From: Sebastian Spaeth Date: Thu, 17 Jan 2013 12:18:14 +0100 Subject: import db.sql.util -> db.util Merging an old branch, I reintroduced an import of db.sql.util rather than db.util. Fixing the glitch. Signed-off-by: Sebastian Spaeth --- mediagoblin/db/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 1c6cbda7..782bf869 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -95,7 +95,7 @@ class User(Base, UserMixin): # Delete now unused tags # TODO: import here due to cyclic imports!!! This cries for refactoring - from mediagoblin.db.sql.util import clean_orphan_tags + from mediagoblin.db.util import clean_orphan_tags clean_orphan_tags(commit=False) # Delete user, pass through commit=False/True in kwargs -- cgit v1.2.3 From dc4dfbde350fdd9eac50448e42f2c8b209dd6ea8 Mon Sep 17 00:00:00 2001 From: Mark Holmquist Date: Sat, 10 Nov 2012 16:59:37 -0800 Subject: Add a license preference field This feature is absolutely necessary. Now a user can simply define their default license and quickly go through a form, as opposed to stopping to click on the select and choosing the same option over and over again. Also added DB migration for the field, so that's working now, too. Rebased by Sebastian and made the default value to be unicode. Reviewed-by: Sebastian Spaeth --- mediagoblin/db/models.py | 1 + 1 file changed, 1 insertion(+) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 782bf869..7e2cc7d2 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -63,6 +63,7 @@ class User(Base, UserMixin): # Intented to be nullable=False, but migrations would not work for it # set to nullable=True implicitly. wants_comment_notification = Column(Boolean, default=True) + license_preference = Column(Unicode) verification_key = Column(Unicode) is_admin = Column(Boolean, default=False, nullable=False) url = Column(Unicode) -- cgit v1.2.3 From 6194344bf9a27d580f21b061a6e4b7c3d17ec4a9 Mon Sep 17 00:00:00 2001 From: Elrond Date: Tue, 22 Jan 2013 22:00:41 +0100 Subject: Use better relationships to delete collections. When deleting a User, his/her collections can be deleted by sqlalchemy: Collections do not need any special code to be executed on deletion. --- mediagoblin/db/models.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 7e2cc7d2..de491e96 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -84,9 +84,7 @@ class User(Base, UserMixin): def delete(self, **kwargs): """Deletes a User and all related entries/comments/files/...""" - # Delete this user's Collections and all contained CollectionItems - for collection in self.collections: - collection.delete(commit=False) + # Collections get deleted by relationships. media_entries = MediaEntry.query.filter(MediaEntry.uploader == self.id) for media in media_entries: @@ -415,7 +413,10 @@ class Collection(Base, CollectionMixin): # TODO: No of items in Collection. Badly named, can we migrate to num_items? items = Column(Integer, default=0) - get_creator = relationship(User, backref="collections") + # Cascade: Collections are owned by their creator. So do the full thing. + get_creator = relationship(User, + backref=backref("collections", + cascade="all, delete-orphan")) def get_collection_items(self, ascending=False): #TODO, is this still needed with self.collection_items being available? @@ -436,7 +437,9 @@ class CollectionItem(Base, CollectionItemMixin): note = Column(UnicodeText, nullable=True) added = Column(DateTime, nullable=False, default=datetime.datetime.now) position = Column(Integer) - in_collection = relationship("Collection", + + # Cascade: CollectionItems are owned by their Collection. So do the full thing. + in_collection = relationship(Collection, backref=backref( "collection_items", cascade="all, delete-orphan")) -- cgit v1.2.3 From ff68ca9fc2b329d1c2ec395abf50358845c4e5fc Mon Sep 17 00:00:00 2001 From: Elrond Date: Tue, 29 Jan 2013 21:23:21 +0100 Subject: Fix issue 611: Proper (back)relationship on MediaComment. well, fix the relationship on the comments. --- mediagoblin/db/models.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index de491e96..101e7cee 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -393,7 +393,13 @@ class MediaComment(Base, MediaCommentMixin): created = Column(DateTime, nullable=False, default=datetime.datetime.now) content = Column(UnicodeText, nullable=False) - get_author = relationship(User) + # Cascade: Comments are owned by their creator. So do the full thing. + # lazy=dynamic: People might post a *lot* of comments, so make + # the "posted_comments" a query-like thing. + get_author = relationship(User, + backref=backref("posted_comments", + lazy="dynamic", + cascade="all, delete-orphan")) class Collection(Base, CollectionMixin): -- cgit v1.2.3 From 57f8d263e1773be7458f09f9b3f1b7571cb0e026 Mon Sep 17 00:00:00 2001 From: Elrond Date: Fri, 1 Feb 2013 15:42:44 +0100 Subject: Rewrite media_data handling to use relationships Instead of doing query by hand, use the relationships on the models to find the media_data. Is is made possible by the BACKREF_NAME in each models.py, which lets us know the local attr to ask for. Also initialize the relationship attribute on new media_data instead of the media_id. Also do not add it to the session. This gives us: - This automatically initializes the other side of the relationship, which will allow later acces via that way. - If the media_data is too early in the session, when the (new) media_entry is not yet in there, this could get conflicts. Avoid those by not adding to session. - Uses cascading to commit media_data together with the media_entry. --- mediagoblin/db/models.py | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 101e7cee..bdd957dd 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -20,7 +20,7 @@ TODO: indexes on foreignkeys, where useful. import logging import datetime -import sys +from collections import Sequence from sqlalchemy import Column, Integer, Unicode, UnicodeText, DateTime, \ Boolean, ForeignKey, UniqueConstraint, PrimaryKeyConstraint, \ @@ -32,9 +32,10 @@ from sqlalchemy.ext.associationproxy import association_proxy from sqlalchemy.util import memoized_property from mediagoblin.db.extratypes import PathTupleWithSlashes, JSONEncoded -from mediagoblin.db.base import Base, DictReadAttrProxy, Session +from mediagoblin.db.base import Base, DictReadAttrProxy from mediagoblin.db.mixin import UserMixin, MediaEntryMixin, MediaCommentMixin, CollectionMixin, CollectionItemMixin from mediagoblin.tools.files import delete_media_files +from mediagoblin.tools.common import import_component # It's actually kind of annoying how sqlalchemy-migrate does this, if # I understand it right, but whatever. Anyway, don't remove this :P @@ -165,7 +166,6 @@ class MediaEntry(Base, MediaEntryMixin): collections = association_proxy("collections_helper", "in_collection") ## TODO - # media_data # fail_error def get_comments(self, ascending=False): @@ -195,40 +195,41 @@ class MediaEntry(Base, MediaEntryMixin): if media is not None: return media.url_for_self(urlgen) - #@memoized_property @property def media_data(self): - session = Session() - - return session.query(self.media_data_table).filter_by( - media_entry=self.id).first() + r = getattr(self, self.media_data_ref, None) + if isinstance(r, Sequence): + assert len(r) < 2 + if r: + return r[0] + else: + return None + return r def media_data_init(self, **kwargs): """ Initialize or update the contents of a media entry's media_data row """ - session = Session() - - media_data = session.query(self.media_data_table).filter_by( - media_entry=self.id).first() + media_data = self.media_data - # No media data, so actually add a new one if media_data is None: + # No media data, so actually add a new one media_data = self.media_data_table( - media_entry=self.id, **kwargs) - session.add(media_data) - # Update old media data + # Get the relationship set up. + media_data.get_media_entry = self else: + # Update old media data for field, value in kwargs.iteritems(): setattr(media_data, field, value) @memoized_property def media_data_table(self): - # TODO: memoize this - models_module = self.media_type + '.models' - __import__(models_module) - return sys.modules[models_module].DATA_MODEL + return import_component(self.media_type + '.models:DATA_MODEL') + + @memoized_property + def media_data_ref(self): + return import_component(self.media_type + '.models:BACKREF_NAME') def __repr__(self): safe_title = self.title.encode('ascii', 'replace') -- cgit v1.2.3 From 139c6c099fdbf139d2441db0c1a774081394a47d Mon Sep 17 00:00:00 2001 From: Elrond Date: Fri, 1 Feb 2013 16:33:53 +0100 Subject: Drop media_data_table property. Only when creating a new media_data row, we need the table. So load that locally in media_data_init(). --- mediagoblin/db/models.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index bdd957dd..c9bc3c11 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -213,9 +213,10 @@ class MediaEntry(Base, MediaEntryMixin): media_data = self.media_data if media_data is None: + # Get the correct table: + table = import_component(self.media_type + '.models:DATA_MODEL') # No media data, so actually add a new one - media_data = self.media_data_table( - **kwargs) + media_data = table(**kwargs) # Get the relationship set up. media_data.get_media_entry = self else: @@ -223,10 +224,6 @@ class MediaEntry(Base, MediaEntryMixin): for field, value in kwargs.iteritems(): setattr(media_data, field, value) - @memoized_property - def media_data_table(self): - return import_component(self.media_type + '.models:DATA_MODEL') - @memoized_property def media_data_ref(self): return import_component(self.media_type + '.models:BACKREF_NAME') -- cgit v1.2.3 From 485404a9c42b09f0fda38aeb8d1242f24ccfa143 Mon Sep 17 00:00:00 2001 From: Elrond Date: Fri, 1 Feb 2013 16:33:53 +0100 Subject: Drop backward compatibility for media_data backref. Now we only support media_type backrefs with uselist=False. --- mediagoblin/db/models.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index c9bc3c11..10e0c33f 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -20,7 +20,6 @@ TODO: indexes on foreignkeys, where useful. import logging import datetime -from collections import Sequence from sqlalchemy import Column, Integer, Unicode, UnicodeText, DateTime, \ Boolean, ForeignKey, UniqueConstraint, PrimaryKeyConstraint, \ @@ -197,14 +196,7 @@ class MediaEntry(Base, MediaEntryMixin): @property def media_data(self): - r = getattr(self, self.media_data_ref, None) - if isinstance(r, Sequence): - assert len(r) < 2 - if r: - return r[0] - else: - return None - return r + return getattr(self, self.media_data_ref) def media_data_init(self, **kwargs): """ -- cgit v1.2.3 From df5b142ab9bfc590f17768079104f6cfa2cd7bba Mon Sep 17 00:00:00 2001 From: Elrond Date: Mon, 18 Feb 2013 14:46:28 +0100 Subject: Fix deleting media with attachments. If one deletes a media with attachments, there have been various problems: 1) If the file in the storage did not exist any more (maybe because due to a previous deletion attempt?), the error propagation failed, because the wrong thing was gathered. 2) The attachment database entries were not deleted. Using cascade for this, for now. Also add a simple unit test, that tests both by having a broken attachment on a media. --- mediagoblin/db/models.py | 1 + 1 file changed, 1 insertion(+) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 10e0c33f..2f58503f 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -145,6 +145,7 @@ class MediaEntry(Base, MediaEntryMixin): ) attachment_files_helper = relationship("MediaAttachmentFile", + cascade="all, delete-orphan", order_by="MediaAttachmentFile.created" ) attachment_files = association_proxy("attachment_files_helper", "dict_view", -- cgit v1.2.3 From b98882e16ee855af0e6d255cb5ec9ab6d800b3f4 Mon Sep 17 00:00:00 2001 From: Elrond Date: Sat, 9 Mar 2013 12:24:15 +0100 Subject: Use cascade for comment deletion. Also use the relationship for getting the comments on a MediaEntry. --- mediagoblin/db/models.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 2f58503f..fcfd0f61 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -172,8 +172,7 @@ class MediaEntry(Base, MediaEntryMixin): order_col = MediaComment.created if not ascending: order_col = desc(order_col) - return MediaComment.query.filter_by( - media_entry=self.id).order_by(order_col) + return self.all_comments.order_by(order_col) def url_to_prev(self, urlgen): """get the next 'newer' entry by this user""" @@ -238,9 +237,7 @@ class MediaEntry(Base, MediaEntryMixin): :param del_orphan_tags: True/false if we delete unused Tags too :param commit: True/False if this should end the db transaction""" # User's CollectionItems are automatically deleted via "cascade". - # Delete all the associated comments - for comment in self.get_comments(): - comment.delete(commit=False) + # Comments on this Media are deleted by cascade, hopefully. # Delete all related files/attachments try: @@ -385,13 +382,22 @@ class MediaComment(Base, MediaCommentMixin): content = Column(UnicodeText, nullable=False) # Cascade: Comments are owned by their creator. So do the full thing. - # lazy=dynamic: People might post a *lot* of comments, so make - # the "posted_comments" a query-like thing. + # lazy=dynamic: People might post a *lot* of comments, + # so make the "posted_comments" a query-like thing. get_author = relationship(User, backref=backref("posted_comments", lazy="dynamic", cascade="all, delete-orphan")) + # Cascade: Comments are somewhat owned by their MediaEntry. + # So do the full thing. + # lazy=dynamic: MediaEntries might have many comments, + # so make the "all_comments" a query-like thing. + get_media_entry = relationship(MediaEntry, + backref=backref("all_comments", + lazy="dynamic", + cascade="all, delete-orphan")) + class Collection(Base, CollectionMixin): """An 'album' or 'set' of media by a user. -- cgit v1.2.3 From 34d8bc9820a945ae6af07764d0ea3b058829fef1 Mon Sep 17 00:00:00 2001 From: Rodney Ewing Date: Tue, 23 Apr 2013 18:28:10 -0700 Subject: Check for duplicate collection slugs and make them unique. Add unique constraint to collection.slug model --- mediagoblin/db/models.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index fcfd0f61..2412706e 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -410,7 +410,7 @@ class Collection(Base, CollectionMixin): title = Column(Unicode, nullable=False) slug = Column(Unicode) created = Column(DateTime, nullable=False, default=datetime.datetime.now, - index=True) + index=True) description = Column(UnicodeText) creator = Column(Integer, ForeignKey(User.id), nullable=False) # TODO: No of items in Collection. Badly named, can we migrate to num_items? @@ -421,6 +421,10 @@ class Collection(Base, CollectionMixin): backref=backref("collections", cascade="all, delete-orphan")) + __table_args__ = ( + UniqueConstraint('creator', 'slug'), + {}) + def get_collection_items(self, ascending=False): #TODO, is this still needed with self.collection_items being available? order_col = CollectionItem.position -- cgit v1.2.3 From fbe8edc21c152f70c8bb3a24830deb394570498c Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Fri, 17 May 2013 14:10:29 -0500 Subject: Noting why we don't have an email uniqueness constraint in the db. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit sponsored by Guido Günther. Thanks! --- mediagoblin/db/models.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'mediagoblin/db/models.py') diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 2412706e..2b925983 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -55,6 +55,10 @@ class User(Base, UserMixin): id = Column(Integer, primary_key=True) username = Column(Unicode, nullable=False, unique=True) + # Note: no db uniqueness constraint on email because it's not + # reliable (many email systems case insensitive despite against + # the RFC) and because it would be a mess to implement at this + # point. email = Column(Unicode, nullable=False) created = Column(DateTime, nullable=False, default=datetime.datetime.now) pw_hash = Column(Unicode, nullable=False) -- cgit v1.2.3