RelatedPost: mapping['thread'] now manage relation between thread and parent of related object
This commit is contained in:
		@ -82,7 +82,37 @@ class RelatedPostBase (models.base.ModelBase):
 | 
			
		||||
    """
 | 
			
		||||
    Metaclass for RelatedPost children.
 | 
			
		||||
    """
 | 
			
		||||
    def __new__ (cls, name, bases, attrs):
 | 
			
		||||
    registry = {}
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def register (cl, key, model):
 | 
			
		||||
        """
 | 
			
		||||
        Register a model and return the key under which it is registered.
 | 
			
		||||
        Raise a ValueError if another model is yet associated under this key.
 | 
			
		||||
        """
 | 
			
		||||
        if key in cl.registry and cl.registry[key] is not model:
 | 
			
		||||
            raise ValueError('A model has yet been registered with "{}"'
 | 
			
		||||
                             .format(key))
 | 
			
		||||
        cl.registry[key] = model
 | 
			
		||||
        return key
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def check_thread_mapping (cl, relation, model, field):
 | 
			
		||||
        """
 | 
			
		||||
        Add information related to the mapping 'thread' info.
 | 
			
		||||
        """
 | 
			
		||||
        if not field:
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        parent_model = model._meta.get_field(field).rel.to
 | 
			
		||||
        thread_model = cl.registry.get(parent_model)
 | 
			
		||||
 | 
			
		||||
        if not thread_model:
 | 
			
		||||
            raise ValueError('no registered RelatedPost for the model {}'
 | 
			
		||||
                             .format(model.__name__))
 | 
			
		||||
        relation.thread_model = thread_model
 | 
			
		||||
 | 
			
		||||
    def __new__ (cl, name, bases, attrs):
 | 
			
		||||
        rel = attrs.get('Relation')
 | 
			
		||||
        rel = (rel and rel.__dict__) or {}
 | 
			
		||||
 | 
			
		||||
@ -96,9 +126,17 @@ class RelatedPostBase (models.base.ModelBase):
 | 
			
		||||
        if name is not 'RelatedPost':
 | 
			
		||||
            _relation = RelatedPost.Relation()
 | 
			
		||||
            _relation.__dict__.update(rel)
 | 
			
		||||
            mapping = rel.get('mapping')
 | 
			
		||||
            cl.check_thread_mapping(
 | 
			
		||||
                _relation,
 | 
			
		||||
                related_model,
 | 
			
		||||
                mapping and mapping.get('thread')
 | 
			
		||||
            )
 | 
			
		||||
            attrs['_relation'] = _relation
 | 
			
		||||
 | 
			
		||||
        return super().__new__(cls, name, bases, attrs)
 | 
			
		||||
        model = super().__new__(cl, name, bases, attrs)
 | 
			
		||||
        cl.register(related_model, model)
 | 
			
		||||
        return model
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class RelatedPost (Post, metaclass = RelatedPostBase):
 | 
			
		||||
@ -108,25 +146,80 @@ class RelatedPost (Post, metaclass = RelatedPostBase):
 | 
			
		||||
        abstract = True
 | 
			
		||||
 | 
			
		||||
    class Relation:
 | 
			
		||||
        related_model = None
 | 
			
		||||
        mapping = None          # dict of related mapping values
 | 
			
		||||
        bind_mapping = False    # update fields of related data on save
 | 
			
		||||
        """
 | 
			
		||||
        Relation descriptor used to generate and manage the related object.
 | 
			
		||||
 | 
			
		||||
        * model: model of the related object
 | 
			
		||||
        * mapping: values that are bound between the post and the related
 | 
			
		||||
            object. When the post is saved, these fields are updated on it.
 | 
			
		||||
            It is a dict of { post_attr: rel_attr }
 | 
			
		||||
 | 
			
		||||
            If there is a post_attr "thread", the corresponding rel_attr is used
 | 
			
		||||
            to update the post thread to the correct Post model.
 | 
			
		||||
        """
 | 
			
		||||
        model = None
 | 
			
		||||
        mapping = None          # values to map { post_attr: rel_attr }
 | 
			
		||||
        bind = False            # update fields of related data on save
 | 
			
		||||
        thread_model = None
 | 
			
		||||
 | 
			
		||||
    def get_attribute (self, attr):
 | 
			
		||||
        attr = self._relation.mappings.get(attr)
 | 
			
		||||
        return self.related.__dict__[attr] if attr else None
 | 
			
		||||
 | 
			
		||||
    def update_thread_mapping (self, save = True):
 | 
			
		||||
        """
 | 
			
		||||
        Update the parent object designed by Relation.mapping.thread if the
 | 
			
		||||
        type matches to the one related of the current instance's thread.
 | 
			
		||||
 | 
			
		||||
        If there is no thread assigned to self, set it to the parent of the
 | 
			
		||||
        related object.
 | 
			
		||||
        """
 | 
			
		||||
        relation = self._relation
 | 
			
		||||
        print(relation.__dict__)
 | 
			
		||||
        thread_model = relation.thread_model
 | 
			
		||||
        if not thread_model:
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        # self.related.parent -> self.thread
 | 
			
		||||
        rel_parent = relation.mapping.get('thread')
 | 
			
		||||
        if not self.thread:
 | 
			
		||||
            rel_parent = getattr(self.related, rel_parent)
 | 
			
		||||
            thread = thread_model.objects.filter(related = rel_parent)
 | 
			
		||||
            if thread.count():
 | 
			
		||||
                self.thread = thread[0]
 | 
			
		||||
                if save:
 | 
			
		||||
                    self.save()
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        # self.thread -> self.related.parent
 | 
			
		||||
        if thread_model is not self.thread_type.model_class():
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        setattr(self.related, rel_parent, self.thread.related)
 | 
			
		||||
        if save:
 | 
			
		||||
            self.save()
 | 
			
		||||
 | 
			
		||||
    def update_mapping (self):
 | 
			
		||||
        relation = self._relation
 | 
			
		||||
        mapping = relation.mapping
 | 
			
		||||
        if not mapping:
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        related = self.related
 | 
			
		||||
        related.__dict__.update({
 | 
			
		||||
            rel_attr: self.__dict__[attr]
 | 
			
		||||
            for attr, rel_attr in mapping.items()
 | 
			
		||||
            if attr is not 'thread' and attr in self.__dict__
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        self.update_thread_mapping(save = False)
 | 
			
		||||
        related.save()
 | 
			
		||||
 | 
			
		||||
    def save (self, *args, **kwargs):
 | 
			
		||||
        if not self.title and self.related:
 | 
			
		||||
            self.title = self.get_attribute('title')
 | 
			
		||||
 | 
			
		||||
        if self._relation.bind_mapping:
 | 
			
		||||
            self.related.__dict__.update({
 | 
			
		||||
                rel_attr: self.__dict__[attr]
 | 
			
		||||
                for attr, rel_attr in self.Relation.mapping.items()
 | 
			
		||||
            })
 | 
			
		||||
            self.related.save()
 | 
			
		||||
 | 
			
		||||
        self.update_mapping()
 | 
			
		||||
        super().save(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -57,8 +57,8 @@ class Route:
 | 
			
		||||
        return ''
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def as_url (cl, model, model_name, view, view_kwargs = None):
 | 
			
		||||
        pattern = '^{}/{}'.format(model_name, cl.name)
 | 
			
		||||
    def as_url (cl, name, model, view, view_kwargs = None):
 | 
			
		||||
        pattern = '^{}/{}'.format(name, cl.name)
 | 
			
		||||
        if cl.url_args:
 | 
			
		||||
            url_args = '/'.join([
 | 
			
		||||
                '(?P<{}>{}){}'.format(
 | 
			
		||||
@ -77,7 +77,7 @@ class Route:
 | 
			
		||||
            kwargs.update(view_kwargs)
 | 
			
		||||
 | 
			
		||||
        return url(pattern, view, kwargs = kwargs,
 | 
			
		||||
                   name = model_name + '_' + cl.name)
 | 
			
		||||
                   name = name + '_' + cl.name)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class DetailRoute (Route):
 | 
			
		||||
 | 
			
		||||
@ -385,50 +385,3 @@ class Sections:
 | 
			
		||||
            return context
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ViewSet:
 | 
			
		||||
    """
 | 
			
		||||
    A ViewSet is a class helper that groups detail and list views that can be
 | 
			
		||||
    used to generate views and routes given a model and a name used for the
 | 
			
		||||
    routing.
 | 
			
		||||
    """
 | 
			
		||||
    model = None
 | 
			
		||||
    list_view = PostListView
 | 
			
		||||
    list_routes = []
 | 
			
		||||
 | 
			
		||||
    detail_view = PostDetailView
 | 
			
		||||
    detail_sections = [
 | 
			
		||||
        Sections.PostContent(),
 | 
			
		||||
        Sections.PostImage(),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def get_list_name (self):
 | 
			
		||||
        """
 | 
			
		||||
        Return a string with the name to use in the route for the lists
 | 
			
		||||
        """
 | 
			
		||||
        return self.model._meta.verbose_name_plural.lower()
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def get_detail_name (cl):
 | 
			
		||||
        """
 | 
			
		||||
        Return a string with the name to use in the route for the details
 | 
			
		||||
        """
 | 
			
		||||
        return cl.model._meta.verbose_name.lower()
 | 
			
		||||
 | 
			
		||||
    def connect (self, website = None):
 | 
			
		||||
        self.detail_view = self.detail_view.as_view(
 | 
			
		||||
            model = self.model,
 | 
			
		||||
            sections = self.detail_sections,
 | 
			
		||||
            website = website,
 | 
			
		||||
        )
 | 
			
		||||
        self.list_view = self.list_view.as_view(
 | 
			
		||||
            model = self.model,
 | 
			
		||||
            website = website,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        self.urls = [ route.as_url(self.model, self.get_list_name(),
 | 
			
		||||
                            self.list_view) for route in self.list_routes ] + \
 | 
			
		||||
                      [ routes.DetailRoute.as_url(self.model,
 | 
			
		||||
                          self.get_detail_name(), self.detail_view ) ]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,5 @@
 | 
			
		||||
import aircox_cms.routes as routes
 | 
			
		||||
import aircox_cms.views as views
 | 
			
		||||
 | 
			
		||||
class Website:
 | 
			
		||||
    name = ''
 | 
			
		||||
@ -12,26 +13,67 @@ class Website:
 | 
			
		||||
                    'right', 'bottom',
 | 
			
		||||
                    'header', 'footer']
 | 
			
		||||
    router = None
 | 
			
		||||
    urls = []
 | 
			
		||||
    registry = {}
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def urls (self):
 | 
			
		||||
        return self.router.get_urlpatterns()
 | 
			
		||||
 | 
			
		||||
    def __init__ (self, **kwargs):
 | 
			
		||||
        self.registry = {}
 | 
			
		||||
        self.urls = []
 | 
			
		||||
        self.__dict__.update(kwargs)
 | 
			
		||||
        if not self.router:
 | 
			
		||||
            self.router = routes.Router()
 | 
			
		||||
 | 
			
		||||
    def register_set (self, view_set):
 | 
			
		||||
    def register_model (self, name, model):
 | 
			
		||||
        """
 | 
			
		||||
        Register a ViewSet (or subclass) to the router,
 | 
			
		||||
        and connect it to self.
 | 
			
		||||
        Register a model and return the name under which it is registered.
 | 
			
		||||
        Raise a ValueError if another model is yet associated under this name.
 | 
			
		||||
        """
 | 
			
		||||
        view_set = view_set()
 | 
			
		||||
        view_set.connect(website = self)
 | 
			
		||||
        self.registry[view_set.get_detail_name()] = view_set
 | 
			
		||||
        self.router.register_set(view_set)
 | 
			
		||||
        if name in self.registry and self.registry[name] is not model:
 | 
			
		||||
            raise ValueError('A model has yet been registered under "{}"'
 | 
			
		||||
                             .format(name))
 | 
			
		||||
        self.registry[name] = model
 | 
			
		||||
        return name
 | 
			
		||||
 | 
			
		||||
    def register_detail (self, name, model, view = views.PostDetailView,
 | 
			
		||||
                         **view_kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        Register a model and the detail view
 | 
			
		||||
        """
 | 
			
		||||
        name = self.register_model(name, model)
 | 
			
		||||
        view = view.as_view(
 | 
			
		||||
            website = self,
 | 
			
		||||
            model = model,
 | 
			
		||||
            **view_kwargs,
 | 
			
		||||
        )
 | 
			
		||||
        self.urls.append(routes.DetailRoute.as_url(name, model, view))
 | 
			
		||||
        self.registry[name] = model
 | 
			
		||||
 | 
			
		||||
    def register_list (self, name, model, view = views.PostListView,
 | 
			
		||||
                       routes = [], **view_kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        Register a model and the given list view using the given routes
 | 
			
		||||
        """
 | 
			
		||||
        name = self.register_model(name, model)
 | 
			
		||||
        view = view.as_view(
 | 
			
		||||
            website = self,
 | 
			
		||||
            model = model,
 | 
			
		||||
            **view_kwargs
 | 
			
		||||
        )
 | 
			
		||||
        self.urls += [ route.as_url(name, model, view) for route in routes ]
 | 
			
		||||
        self.registry[name] = model
 | 
			
		||||
 | 
			
		||||
    def register (self, name, model, sections = None, routes = None,
 | 
			
		||||
                  list_kwargs = {}, detail_kwargs = {}):
 | 
			
		||||
        if sections:
 | 
			
		||||
            self.register_detail(
 | 
			
		||||
                name, model,
 | 
			
		||||
                sections = sections,
 | 
			
		||||
                **detail_kwargs
 | 
			
		||||
            )
 | 
			
		||||
        if routes:
 | 
			
		||||
            self.register_list(
 | 
			
		||||
                name, model,
 | 
			
		||||
                routes = routes,
 | 
			
		||||
                **list_kwargs
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
    def get_menu (self, position):
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
@ -17,6 +17,7 @@ class Episode (RelatedPost):
 | 
			
		||||
        related_model = programs.Episode
 | 
			
		||||
        bind_mapping = True
 | 
			
		||||
        mapping = {
 | 
			
		||||
            'thread': 'program',
 | 
			
		||||
            'title': 'name',
 | 
			
		||||
            'content': 'description',
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -4,42 +4,10 @@ from website.models import *
 | 
			
		||||
from website.views import *
 | 
			
		||||
 | 
			
		||||
from aircox_cms.models import Article
 | 
			
		||||
from aircox_cms.views import Menu, Section, Sections, ViewSet
 | 
			
		||||
from aircox_cms.views import Menu, Section, Sections
 | 
			
		||||
from aircox_cms.routes import *
 | 
			
		||||
from aircox_cms.website import Website
 | 
			
		||||
 | 
			
		||||
class ProgramSet (ViewSet):
 | 
			
		||||
    model = Program
 | 
			
		||||
    list_routes = [
 | 
			
		||||
        AllRoute,
 | 
			
		||||
        ThreadRoute,
 | 
			
		||||
        SearchRoute,
 | 
			
		||||
        DateRoute,
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    detail_sections = ViewSet.detail_sections + [
 | 
			
		||||
        ScheduleSection(),
 | 
			
		||||
        EpisodesSection(),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
class EpisodeSet (ViewSet):
 | 
			
		||||
    model = Episode
 | 
			
		||||
    list_routes = [
 | 
			
		||||
        AllRoute,
 | 
			
		||||
        ThreadRoute,
 | 
			
		||||
        SearchRoute,
 | 
			
		||||
        DateRoute,
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
class ArticleSet (ViewSet):
 | 
			
		||||
    model = Article
 | 
			
		||||
    list_routes = [
 | 
			
		||||
        AllRoute,
 | 
			
		||||
        ThreadRoute,
 | 
			
		||||
        SearchRoute,
 | 
			
		||||
        DateRoute,
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
website = Website(
 | 
			
		||||
    name = 'RadioCampus',
 | 
			
		||||
@ -70,8 +38,40 @@ website = Website(
 | 
			
		||||
    ],
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
website.register_set(ProgramSet)
 | 
			
		||||
website.register_set(EpisodeSet)
 | 
			
		||||
website.register_set(ArticleSet)
 | 
			
		||||
base_sections = [
 | 
			
		||||
    Sections.PostContent(),
 | 
			
		||||
    Sections.PostImage(),
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
base_routes =  [
 | 
			
		||||
    AllRoute,
 | 
			
		||||
    ThreadRoute,
 | 
			
		||||
    SearchRoute,
 | 
			
		||||
    DateRoute,
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
website.register(
 | 
			
		||||
    'article',
 | 
			
		||||
    Article,
 | 
			
		||||
    sections = base_sections,
 | 
			
		||||
    routes = base_routes
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
website.register(
 | 
			
		||||
    'program',
 | 
			
		||||
    Program,
 | 
			
		||||
    sections = base_sections + [
 | 
			
		||||
        ScheduleSection(),
 | 
			
		||||
        EpisodesSection(),
 | 
			
		||||
    ],
 | 
			
		||||
    routes = base_routes,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
website.register (
 | 
			
		||||
    'episode',
 | 
			
		||||
    Episode,
 | 
			
		||||
    sections = base_sections,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
urlpatterns = website.urls
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user