Switch to unified view

a/Allura/allura/model/artifact.py b/Allura/allura/model/artifact.py
...
...
214
                for l in links:
214
                for l in links:
215
                    if app_id == l.tool_name: return l
215
                    if app_id == l.tool_name: return l
216
        return None
216
        return None
217
217
218
class Feed(MappedClass):
218
class Feed(MappedClass):
219
    """
220
    Used to generate rss/atom feeds.  This does not need to be extended;
221
    all feed items go into the same collection
222
    """
219
    class __mongometa__:
223
    class __mongometa__:
220
        session = project_orm_session
224
        session = project_orm_session
221
        name = 'artifact_feed'
225
        name = 'artifact_feed'
222
        indexes = [
226
        indexes = [
223
            'pubdate',
227
            'pubdate',
...
...
233
    author_name = FieldProperty(str, if_missing=lambda:c.user.get_pref('display_name') if hasattr(c, 'user') else None)
237
    author_name = FieldProperty(str, if_missing=lambda:c.user.get_pref('display_name') if hasattr(c, 'user') else None)
234
    author_link = FieldProperty(str, if_missing=lambda:c.user.url() if hasattr(c, 'user') else None)
238
    author_link = FieldProperty(str, if_missing=lambda:c.user.url() if hasattr(c, 'user') else None)
235
239
236
    @classmethod
240
    @classmethod
237
    def post(cls, artifact, title=None, description=None):
241
    def post(cls, artifact, title=None, description=None):
242
        "Create a Feed item"
238
        idx = artifact.index()
243
        idx = artifact.index()
239
        if title is None:
244
        if title is None:
240
            title='%s modified by %s' % (idx['title_s'], c.user.get_pref('display_name'))
245
            title='%s modified by %s' % (idx['title_s'], c.user.get_pref('display_name'))
241
        if description is None: description = title
246
        if description is None: description = title
242
        item = cls(artifact_reference=artifact.dump_ref(),
247
        item = cls(artifact_reference=artifact.dump_ref(),
...
...
246
        return item
251
        return item
247
252
248
    @classmethod
253
    @classmethod
249
    def feed(cls, q, feed_type, title, link, description,
254
    def feed(cls, q, feed_type, title, link, description,
250
             since=None, until=None, offset=None, limit=None):
255
             since=None, until=None, offset=None, limit=None):
256
        "Produces webhelper.feedgenerator Feed"
251
        d = dict(title=title, link=h.absurl(link), description=description, language=u'en')
257
        d = dict(title=title, link=h.absurl(link), description=description, language=u'en')
252
        if feed_type == 'atom':
258
        if feed_type == 'atom':
253
            feed = FG.Atom1Feed(**d)
259
            feed = FG.Atom1Feed(**d)
254
        elif feed_type == 'rss':
260
        elif feed_type == 'rss':
255
            feed = FG.Rss201rev2Feed(**d)
261
            feed = FG.Rss201rev2Feed(**d)
...
...
273
                          author_name=r.author_name,
279
                          author_name=r.author_name,
274
                          author_link=h.absurl(r.author_link))
280
                          author_link=h.absurl(r.author_link))
275
        return feed
281
        return feed
276
282
277
class Artifact(MappedClass):
283
class Artifact(MappedClass):
284
    """
285
    The base class for anything you want to keep track of.
286
287
    It will automatically be added to solr (see index() method).  It also
288
    gains a discussion thread and can have files attached to it.
289
290
    :var tool_version: default's to the app's version
291
    :var acl: dict of permission name => [roles]
292
    :var labels: list of plain old strings
293
    :var references: list of outgoing references to other tickets
294
    :var backreferences: dict of incoming references to this artifact, mapped by solr id
295
    """
296
278
    class __mongometa__:
297
    class __mongometa__:
279
        session = artifact_orm_session
298
        session = artifact_orm_session
280
        name='artifact'
299
        name='artifact'
281
        indexes = [ 'app_config_id' ]
300
        indexes = [ 'app_config_id' ]
282
        def before_save(data):
301
        def before_save(data):
...
...
299
        if_missing=lambda:{c.app.config.tool_name:c.app.__version__})
318
        if_missing=lambda:{c.app.config.tool_name:c.app.__version__})
300
    acl = FieldProperty({str:[S.ObjectId]})
319
    acl = FieldProperty({str:[S.ObjectId]})
301
    tags = FieldProperty([dict(tag=str, count=int)])
320
    tags = FieldProperty([dict(tag=str, count=int)])
302
    labels = FieldProperty([str])
321
    labels = FieldProperty([str])
303
    references = FieldProperty([ArtifactReferenceType])
322
    references = FieldProperty([ArtifactReferenceType])
304
    backreferences = FieldProperty({str:ArtifactReferenceType})
323
    backreferences = FieldProperty({str:ArtifactReferenceType}) # keyed by solr id to emulate a set
305
    app_config = RelationProperty('AppConfig')
324
    app_config = RelationProperty('AppConfig')
306
325
307
    @classmethod
326
    @classmethod
308
    def attachment_class(cls):
327
    def attachment_class(cls):
309
        raise NotImplementedError, 'attachment_class'
328
        raise NotImplementedError, 'attachment_class'
...
...
429
            l = self.acl.setdefault(at, [])
448
            l = self.acl.setdefault(at, [])
430
            if project_role_id in l:
449
            if project_role_id in l:
431
                l.remove(project_role_id)
450
                l.remove(project_role_id)
432
451
433
    def index_id(self):
452
    def index_id(self):
453
        "Used for solr search indexing"
434
        id = '%s.%s#%s' % (
454
        id = '%s.%s#%s' % (
435
            self.__class__.__module__,
455
            self.__class__.__module__,
436
            self.__class__.__name__,
456
            self.__class__.__name__,
437
            self._id)
457
            self._id)
438
        return id.replace('.', '/')
458
        return id.replace('.', '/')
439
459
440
    def index(self):
460
    def index(self):
461
        """
462
        Subclasses should override this, providing a dictionary of solr_field => value.
463
        These fields & values will be stored by solr.  Subclasses should call the
464
        super() index() and then extend it with more fields.  All these fields will be
465
        included in the 'text' field (done by search.solarize())
466
467
        The _s and _t suffixes, for example, follow solr dynamic field naming pattern.
468
        """
469
441
        project = self.project
470
        project = self.project
442
        return dict(
471
        return dict(
443
            id=self.index_id(),
472
            id=self.index_id(),
444
            mod_date_dt=self.mod_date,
473
            mod_date_dt=self.mod_date,
445
            title_s='Artifact %s' % self._id,
474
            title_s='Artifact %s' % self._id,
...
...
454
            tags_t=' '.join(t['tag'] for t in self.tags),
483
            tags_t=' '.join(t['tag'] for t in self.tags),
455
            labels_t=' '.join(l for l in self.labels),
484
            labels_t=' '.join(l for l in self.labels),
456
            snippet_s='')
485
            snippet_s='')
457
486
458
    def url(self):
487
    def url(self):
488
        """
489
        Subclasses should implement this, providing the URL to the artifact
490
        """
459
        raise NotImplementedError, 'url' # pragma no cover
491
        raise NotImplementedError, 'url' # pragma no cover
460
492
461
    def shorthand_id(self):
493
    def shorthand_id(self):
462
        '''How to refer to this artifact within the app instance context.
494
        '''How to refer to this artifact within the app instance context.
463
495
...
...
482
            filename=filename,
514
            filename=filename,
483
            fp=fp, artifact_id=self._id, **kw)
515
            fp=fp, artifact_id=self._id, **kw)
484
        return att
516
        return att
485
517
486
class Snapshot(Artifact):
518
class Snapshot(Artifact):
519
    """A snapshot of an :class:`Artifact <allura.model.Artifact>`, used in :class:`VersionedArtifact <allura.model.VersionedArtifact>`"""
487
    class __mongometa__:
520
    class __mongometa__:
488
        session = artifact_orm_session
521
        session = artifact_orm_session
489
        name='artifact_snapshot'
522
        name='artifact_snapshot'
490
        unique_indexes = [ ('artifact_class', 'artifact_id', 'version') ]
523
        unique_indexes = [ ('artifact_class', 'artifact_id', 'version') ]
491
        indexes = [ ('artifact_id', 'version') ]
524
        indexes = [ ('artifact_id', 'version') ]
...
...
522
555
523
    def __getattr__(self, name):
556
    def __getattr__(self, name):
524
        return getattr(self.data, name)
557
        return getattr(self.data, name)
525
558
526
class VersionedArtifact(Artifact):
559
class VersionedArtifact(Artifact):
560
    """
561
    An :class:`Artifact <allura.model.Artifact>` that has versions.
562
    Associated data like attachments and discussion thread are not versioned.
563
    """
527
    class __mongometa__:
564
    class __mongometa__:
528
        session = artifact_orm_session
565
        session = artifact_orm_session
529
        name='versioned_artifact'
566
        name='versioned_artifact'
530
        history_class = Snapshot
567
        history_class = Snapshot
531
568
...
...
592
            return self.history().first().timestamp
629
            return self.history().first().timestamp
593
        else:
630
        else:
594
            return self.mod_date
631
            return self.mod_date
595
632
596
class Message(Artifact):
633
class Message(Artifact):
634
    """
635
    A message
636
637
    :var _id: an email friendly (e.g. message-id) string id
638
    :var slug: slash-delimeted random identifier.  Slashes useful for threaded searching and ordering
639
    :var full_slug: string of slash-delimited "timestamp:slug" components.  Useful for sorting by timstamp
640
    """
641
597
    class __mongometa__:
642
    class __mongometa__:
598
        session = artifact_orm_session
643
        session = artifact_orm_session
599
        name='message'
644
        name='message'
600
        indexes = Artifact.__mongometa__.indexes + [ 'slug', 'parent_id', 'timestamp' ]
645
        indexes = Artifact.__mongometa__.indexes + [ 'slug', 'parent_id', 'timestamp' ]
601
    type_s='Generic Message'
646
    type_s='Generic Message'
...
...
709
754
710
    def shorthand_id(self):
755
    def shorthand_id(self):
711
        return self.short
756
        return self.short
712
757
713
class AwardGrant(Artifact):
758
class AwardGrant(Artifact):
759
    "An :class:`Award <allura.model.Award>` can be bestowed upon a project by a neighborhood"
714
    class __mongometa__:
760
    class __mongometa__:
715
        session = main_orm_session
761
        session = main_orm_session
716
        name='grant'
762
        name='grant'
717
        indexes = [ 'short' ]
763
        indexes = [ 'short' ]
718
    type_s = 'Generic Award Grant'
764
    type_s = 'Generic Award Grant'