📜 ⬆️ ⬇️

Django widgets and a couple more tricks



Everyone knows that Django is a great development framework, with a bunch of powerful batteries. Personally for me, when I first met django, everything seemed to be extremely convenient - all for the convenience of the developer, I thought. But those who are forced to work with him for a long time know that not everything is as fabulous as it seems to a beginner. As time went on, projects became larger, more difficult, writing views became inconvenient, and understanding the relationship between models became more difficult and more difficult. But work is work, the project was large and complex, and, besides, it was necessary to have a page management system like in cms, and, it seems, there is a wonderful django cms, to which all you need is to write plug-ins. But it turned out that the whole process can be made somewhat more convenient by adding a couple of features and some code.

In this small article, you will not see ready-made recipes, the purpose of the article is to share your ideas. The code examples serve only to help in explaining the key elements; without significant improvements, this will not be enough to repeat the functionality. But if the topic turns out to be interesting, it can be continued in the next article. Or even put in open source.
')

Models


Suppose we have 2 models with common fields: title, description, and tags. If we just need to print the latest materials from both models sorted by date of creation, then the easiest way is to combine them into one model. And in order that they do not merge into one entity in the admin area, we can use the Generic Foreign Key .
For the admin, set up inline editing Info and immediately add a GFKManager snippet for optimizing queries:

from django.db import models from core.container.manager import GFKManager class Info(models.Model): objects = GFKManager() title = models.CharField( max_length=256, blank=True, null=True ) header = models.TextField( max_length=500, blank=True, null=True ) tags = models.ManyToManyField( 'self', symmetrical=False, blank=True, null=True ) def content_type_name(self): return self.content_type.model_class()._meta.verbose_name class Model(models.Model): info = CustomGenericRelation( 'Info', related_name="%(class)s_info" ) class A(Model): field = models.CharField( max_length=256, blank=True, null=True ) class B(Model): pass 


Note that you may get an error when deleting objects of models A and B if you use generic.GenericRelation. Unfortunately I can not find the source:
 # -*- coding: utf-8 -*- from django.contrib.contenttypes import generic from django.db.models.related import RelatedObject from south.modelsinspector import add_introspection_rules class CustomGenericRelation(generic.GenericRelation): def contribute_to_related_class(self, cls, related): super(CustomGenericRelation, self).contribute_to_related_class(cls, related) if self.rel.related_name and not hasattr(self.model, self.rel.related_name): rel_obj = RelatedObject(cls, self.model, self.rel.related_name) setattr(cls, self.rel.related_name, rel_obj) add_introspection_rules([ ( [CustomGenericRelation], [], {}, ), ], ["^core\.ext\.fields\.generic\.CustomGenericRelation"]) 


Now you can easily execute the query:
 Info.objects.filter(content_type__in=(CT.models.A, CT.models.B)) 


For convenience, I use the ContentType map:
 rom django.contrib.contenttypes.models import ContentType from django.db import models from models import Model class Inner(object): def __get__(self, name): return getattr(self.name) class ContentTypeMap(object): __raw__ = {} def __get__(self, obj, addr): path = addr.pop(0) if not hasattr(obj, path): setattr(obj, path, type(path, (object,), {'parent': obj})) attr = getattr(obj, path) return self.__get__(attr, addr) if addr else attr def __init__(self): for model in filter(lambda X: issubclass(X, Model), models.get_models()): content_type = ContentType.objects.get_for_model(model) obj = self.__get__(self, model.__module__.split('.')) self.__raw__[content_type.model] = content_type.id setattr(obj, '%s' % model.__name__, content_type) for obj in map(lambda X: self.__get__(self, X.__module__.split('.')), filter(lambda X: issubclass(X, Model), models.get_models())): setattr(obj.parent, obj.__name__, obj()) CT = ContentTypeMap() 


If we need to organize a search (sphinx) then we can connect django-sphinx to Info. Now, in one request, we can get the tape, search, selection by tags and so on. The disadvantage of this approach is that all fields for which it is necessary to filter queries should be stored in Info, and in the models themselves only those fields for which the filter is not needed, for example, pictures.

Django CMS, plugins and widgets


With the help of CMS, we can add new pages, edit and delete old ones, add widgets to the page, form sidebars and so on. But sometimes, more precisely, quite often there is a need to permanently add a plugin to a template so that it is visible on all pages. django widgets is a solution to our problems, with the help of the include_widget tag we will be able to add everything we need and where we need it. Even more often, it is necessary to get some data in a plugin with ajax. Use tastypie .

 from django.conf.urls.defaults import * from django.http import HttpResponseForbidden from django_widgets.loading import registry from sekizai.context import SekizaiContext from tastypie.resources import Resource from tastypie.utils import trailing_slash from tastypie.serializers import Serializer from core.widgets.cms_plugins import PLUGIN_TEMPLATE_MAP from core.ext.decorator import api_require_request_parameters class HtmlSreializer(Serializer): def to_html(self, data, options=None): return data class WidgetResource(Resource): class Meta: resource_name = 'widget' include_resource_uri = False serializer = HtmlSreializer(formats=['html']) def prepend_urls(self): return [ url(r"^(?P<resource_name>%s)/render%s$" % (self._meta.resource_name, trailing_slash()), self.wrap_view('render'), name="api_render") ] @api_require_request_parameters(['template']) def render(self, request, **kwargs): data = dict(request.GET) template = data.pop('template')[0] if 'widget' in data: widget = registry.get(data.pop('widget')[0]) else: if template not in PLUGIN_TEMPLATE_MAP: return HttpResponseForbidden() widget = PLUGIN_TEMPLATE_MAP[template] data = dict(map(lambda (K, V): (K.rstrip('[]'), V) if K.endswith('[]') else (K.rstrip('[]'), V[0]), data.items())) return self.create_response( request, widget.render(SekizaiContext({'request': request}), template, data, relative_template_path=False) ) def obj_get_list(self, bundle, **kwargs): return [] 

By passing the parameters of the widget name and template in the request, we can get a rendered context. Here I use the variable PLUGIN_TEMPLATE_MAP so that I can only transmit the name of the template.

It remains to link widgets and plugins. There is quite a big piece, but the most important one.
 import os import json from django import forms from django.conf import settings from django_widgets.loading import registry from cms.models import CMSPlugin from cms.plugin_base import CMSPluginBase from cms.plugin_pool import plugin_pool from core.widgets.widgets import ItemWidget PLUGIN_MAP = {} PLUGIN_CT_MAP = {} PLUGIN_TEMPLATE_MAP = {} class PluginWrapper(CMSPluginBase): admin_preview = False class FormWrapper(forms.ModelForm): widget = None templates_available = () def __init__(self, *args, **kwargs): super(FormWrapper, self).__init__(*args, **kwargs) if not self.fields['template'].initial: # TODO self.fields['template'].initial = self.widget.default_template self.fields['template'].help_text = 'at PROJECT_ROOT/templates/%s' % self.widget.get_template_folder() if self.templates_available: self.fields['template'].widget = forms.Select() self.fields['template'].widget.choices = self.templates_available self.__extra_fields__ = set(self.fields.keys()) - set(self._meta.model._meta.get_all_field_names()) data = json.loads(self.instance.data or '{}') if self.instance else {} for key, value in data.items(): self.fields[key].initial = value def clean(self): cleaned_data = super(FormWrapper, self).clean() cleaned_data['data'] = json.dumps(dict( map( lambda K: (K, cleaned_data[K]), filter( lambda K: K in cleaned_data, self.__extra_fields__ ) ) )) return cleaned_data class Meta: model = CMSPlugin widgets = { 'data': forms.HiddenInput() } def get_templates_available(widget): template_folder = widget.get_template_folder() real_folder = os.path.join(settings.TEMPLATE_DIRS[0], *template_folder.split('/')) result = () if os.path.exists(real_folder): for path, dirs, files in os.walk(real_folder): if path == real_folder: choices = filter(lambda filename: filename.endswith('html'), files) result = zip(choices, choices) rel_folder = '%(template_folder)s%(inner_path)s' % { 'template_folder': template_folder, 'inner_path': path.replace(real_folder, '') } for filename in files: PLUGIN_TEMPLATE_MAP['/'.join((rel_folder, filename))] = widget return result def register_plugin(widget, plugin): plugin_pool.register_plugin(plugin) PLUGIN_MAP[widget.__class__] = plugin if issubclass(widget.__class__, ItemWidget): for content_type in widget.__class__.content_types: if content_type not in PLUGIN_CT_MAP: PLUGIN_CT_MAP[content_type] = [] PLUGIN_CT_MAP[content_type].append(plugin) def get_plugin_form(widget, widget_name): return type('FormFor%s' % widget_name, (FormWrapper,), dict(map( lambda (key, options): (key, (options.pop('field') if 'field' in options else forms.CharField)(initial=getattr(widget, key, None), **options)), getattr(widget, 'kwargs', {}).items() ) + [('widget', widget), ('templates_available', get_templates_available(widget))])) def register_plugins(widgets): for widget_name, widget in widgets: if getattr(widget, 'registered', False): continue name = 'PluginFor%s' % widget_name plugin = type( name, (PluginWrapper,), { 'name': getattr(widget, 'name', widget_name), 'widget': widget, 'form': get_plugin_form(widget, widget_name) } ) register_plugin(widget, plugin) register_plugins(registry.widgets.items()) 


Some more tasty batteries




Well, quite banal things:


Conclusion


I tried to explain two key points that can be simplified in working with django, I wanted to explain more, but the article is too voluminous. Other interesting points are the processing and the formation of dynamic URLs, as well as the two main widgets - the ribbon widget and the entity widget, but this is next time. So with this concept, I


Thanks for attention!

Source: https://habr.com/ru/post/228411/


All Articles