Djangoウィジェットとさらにいくつかのトリック





Djangoは多くの強力なバッテリーを備えた優れた開発フレームワークであることを誰もが知っています。 私にとって個人的には、最初にdjangoに出会ったとき、すべてが非常に便利に思えました-すべてが開発者の便宜のためだったと思いました。 しかし、長い間彼と一緒に働くことを余儀なくされている人は、初心者にとってはすべてが素晴らしいとは限らないことを知っています。 時間が経つにつれて、プロジェクトは大きくなり、困難になり、ビューを書くことは不快になり、モデルの関係を理解することはますます難しくなりました。 しかし、仕事は仕事であり、プロジェクトは大規模で複雑であり、他のすべてのものでは、cmsのようなページ管理システムが必要でした。 しかし、いくつかの機能と少しのコードを追加することで、プロセス全体をもう少し便利にできることがわかりました。



この短い記事では、既製のレシピは表示されませんが、この記事の目的はアイデアを共有することです。 コード例は重要な要素を説明するのに役立ちますが、大幅な改善がなければ機能を繰り返すのに十分ではありません。 しかし、このトピックが興味深いことが判明した場合は、次の記事に進むことができます。 または、オープンソースに入れてください。



モデル



共通のフィールドを持つ2つのモデル、タイトル、説明、タグがあるとします。 作成日でソートされた両方のモデルの最新のマテリアルをフィードに表示する必要がある場合、最も簡単な方法はそれらを1つのモデルに結合することです。 管理パネルで1つのエンティティにマージされないように、 Generic Foreign Keyを使用できます。

管理パネルでは、Infoのインライン編集を構成し、すぐにGFKManagerを追加します -クエリを最適化するためのスニペット:



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
      
      







generic.GenericRelationを使用すると、モデルAおよびBのオブジェクトを削除するときにエラーが発生する可能性があることに注意してください。 残念ながら、私はソースを見つけることができません:

 # -*- 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"])
      
      







これで、クエリを簡単に実行できます。

 Info.objects.filter(content_type__in=(CT.models.A, CT.models.B))
      
      







便宜上、ContentTypeマップを使用します。

 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()
      
      







検索(sphinx)を整理する必要がある場合は、django-sphinxをInfoに接続できます。 これで、1つのリクエストで、フィードの取得、検索、タグによるフィルターなどを実行できます。 このアプローチの欠点は、リクエストをフィルタリングするために必要なすべてのフィールドがInfoに格納され、モデル自体にはフィルターが不要なフィールド(写真など)のみが格納されることです。



Django CMS、プラグイン、ウィジェット



CMSを使用して、新しいページの追加、古いページの編集と削除、ページへのウィジェットの追加、サイドバーの作成などを行うことができます。 しかし、時には、むしろかなり頻繁に、すべてのページに表示されるようにテンプレートにプラグインを永続的に追加する必要があります。 djangoウィジェットは問題の解決策です。include_widgetタグの助けを借りて、必要なものをすべて、必要な場所に追加できます。 さらに頻繁に、ajaxを使用してデータをプラグインに取り込む必要があります。 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 []
      
      





リクエストでウィジェットとテンプレート名のパラメーターを渡すと、レンダリングされたコンテキストを取得できます。 ここでは、テンプレートの名前のみを渡すことができるように、PLUGIN_TEMPLATE_MAP変数を使用します。



ウィジェットとプラグインを接続するために残ります。 これはかなり大きな部分ですが、最も重要な部分です。

 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())
      
      







いくつかのよりおいしいバッテリー







よくありがちなこと:





おわりに



djangoでの作業を簡略化できる2つの重要なポイントを説明しようとしました。もっと説明したかったのですが、記事が長すぎます。 他の興味深い点は、動的URLの処理と形成、および2つのメインウィジェット(リボンウィジェットとエンティティウィジェット)です。しかし、これは次回です。 だから、このコンセプトで、私は





ご清聴ありがとうございました!



All Articles