كيفية تحويل Django Admin إلى لوحة القيادة خفيفة الوزن

للحصول على تجربة قراءة أفضل ، راجع هذه المقالة على موقع الويب الخاص بي.

Django Admin هو أداة قوية لإدارة البيانات في تطبيقك. ومع ذلك ، لم يتم تصميمه مع وضع الجداول والجداول الموجزة في الاعتبار. لحسن الحظ ، فإن مطوري Django Admin جعلوا من السهل علينا التخصيص.

سنقوم بتحويل Django Admin إلى لوحة معلومات عن طريق إضافة مخطط وجدول ملخص.

هذا ما سيبدو عليه في النهاية:

لماذا أريد أن أفعل ذلك؟

هناك الكثير من الأدوات والتطبيقات والحزم التي يمكن أن تنتج لوحات معلومات جميلة المظهر. لقد وجدت شخصياً أنه ما لم يكن المنتج عبارة عن لوحة معلومات فعلية ، فغالبًا ما يكون كل ما تحتاجه هو جدول ملخص بسيط وبعض المخططات البيانية.

الثانية ، وبنفس القدر من الأهمية - لا التبعيات.

إذا كان كل ما تحتاجه هو القليل من الدعم لواجهة المشرف الخاص بك ، فهذا بالتأكيد يستحق النظر.

اقامة

نحن نستخدم نموذج بيع مكياج.

لتسخير القوة الكاملة لـ Django Admin ، سنقوم بإعداد لوحة معلوماتنا على طراز ModelAdmin المدمج.

للقيام بذلك نحتاج إلى نموذج:

# نماذج
بيع فئةخلاصة (بيع):
    الفئة الوصفية:
        الوكيل = صحيح
        verbose_name = 'ملخص البيع'
        verbose_name_plural = "ملخص المبيعات"

يعمل نموذج الوكيل على توسيع وظيفة طراز آخر دون إنشاء جدول فعلي في قاعدة البيانات.

الآن بعد أن أصبح لدينا نموذج يمكننا إنشاء ModelAdmin:

# admin.py
من django.contrib استيراد المشرف
من .النماذج استيراد SaleSummary
@ admin.register (SaleSummary)
بيع فئةخلاصة admin (نموذج admin):
    change_list_template = 'admin / sale_summary_change_list.html'
    date_hierarchy = 'تم إنشاؤه'

لأننا نستخدم ModelAdmin قياسي ، يمكننا استخدام ميزاته. أضفت في هذا المثال تاريخًا هرميًا لتصفية المبيعات حسب تاريخ الإنشاء. سوف نستخدم هذا لاحقًا للمخطط.

للحفاظ على الصفحة تبدو وكأنها صفحة إدارة "عادية" ، نقوم بتوسيع قالب change_list الخاص بـ Django ووضع المحتوى الخاص بنا في كتلة قائمة النتائج:

# sales / templates / admin / sale_summary_change_list.html
{٪ يمتد "admin / change_list.html"٪}
{٪ block content_title٪}
    

ملخص المبيعات {٪ endblock٪}

{٪ block result_list٪}
    المحتوى لدينا يذهب هنا ...
{٪ endblock٪}
{٪ block pagination٪} {٪ endblock٪}

هذا ما تبدو عليه صفحتنا في هذه المرحلة:

إضافة جدول ملخص

يتم ملء السياق المرسل إلى القالب بواسطة ModelAdmin في دالة تسمى changelist_view.

لتقديم الجدول في القالب ، نقوم بجلب البيانات في changelist_view وإضافته إلى السياق:

# admin.py
بيع فئةخلاصة admin (نموذج admin):
    
    ...
    def changelist_view (self، request، extra_context = None):
        استجابة = super (). changelist_view (
            طلب،
            extra_context = extra_context،
        )
        محاولة:
            qs = response.context_data [‘cl ']. queryset
        باستثناء (AttributeError ، KeyError):
            استجابة عودة
        
        المقاييس = {
            "الإجمالي": عدد (معرف) ،
            "total_sales": Sum ("price") ،
        }
        response.context_data [‘summary’] = قائمة (
            QS
            .values ​​( 'sale__category__name')
            .annotate (** المقاييس)
            .order_by ( '- total_sales')
        )
        
        استجابة عودة

دعنا نقسمها:

  1. اتصل بـ Super للسماح لـ Django بالقيام بعمله (رؤوس السكان ، فتات الخبز ، queryset ، المرشحات ، إلخ).
  2. استخراج queryset إنشاؤها بالنسبة لنا من السياق. في هذه المرحلة ، يتم تصفية الاستعلام باستخدام أي عوامل تصفية مضمنة أو تسلسل هرمي للتاريخ يحدده المستخدم.
  3. إذا لم نتمكن من إحضار مجموعة Queryset من السياق ، فمن المحتمل أن يكون ذلك بسبب معلمات استعلام غير صالحة. في حالات مثل إعادة توجيه Django ، فإننا لا نتدخل ونعيد الرد.
  4. تجميع إجمالي المبيعات حسب الفئة وإرجاع قائمة (سيظهر إملاء "المقاييس" في القسم التالي).

الآن بعد أن أصبح لدينا البيانات في السياق ، يمكننا تقديمها في القالب:

# sale_summary_change_list.html
{٪ تحميل أنسنة٪}
...
{٪ block result_list٪}
                                  <ال>           
             الفئة                             <ال>           
             المجموع                             <ال>           
             إجمالي المبيعات                             <ال>           
                           ٪ من إجمالي المبيعات                                             
    
      {٪ للصف في الملخص٪}                                           
{{row.sale__category__name}} {{row.total | intcomma}} {{row.total_sales | الافتراضي: 0 | intcomma}} $           على           {{row.total_sales |               الافتراضي: 0 |               النسبة المئوية: summary_total.total_sales}}                                  {٪ endfor٪}             
...
{٪ endblock٪}

الترميز مهم - لإلقاء نظرة على جانغو الأصلي ، نحتاج إلى تقديم الجداول بالطريقة نفسها التي يعرض بها جانغو.

هذا ما لدينا حتى الآن:

جدول الملخص ليس كثيرًا بدون الخلاصة. يمكننا استخدام المقاييس والقيام ببعض الفودو في Django ORM لحساب الخط الأساسي بسرعة:

# admin.py
بيع فئةخلاصة admin (نموذج admin):
    ...
    
    def changelist_view (self، request، extra_context = None):
        ...
        response.context_data [‘summary_total '] = dict (
            qs.aggregate (** المقاييس)
        )
        استجابة عودة

هذه خدعة رائعة ...

يتيح إضافة الخلاصة إلى الجدول:

             ...
        
                                                            
    

بدأ هذا في التبلور:

مضيفا المرشحات

نحن نستخدم مشرفًا "عاديًا" للنماذج ، لذلك يتم بالفعل تصفية المرشحات.

دعنا تصفية حسب الجهاز:

# admin.py
بيع فئةخلاصة admin (نموذج admin):
    
    ...
    
    list_filter = (
        'جهاز'،
    )

والنتيجة:

إضافة مخطط

لوحة القيادة ليست كاملة دون مخطط.

سنقوم بإضافة مخطط شريطي لعرض المبيعات مع مرور الوقت.

لبناء مخططنا ، سنستخدم HTML عادي وبعض قواعد بيانات ol جيدة CSS مع flexbox. ستكون بيانات المخطط سلسلة زمنية من النسب المئوية لاستخدامها في ارتفاع الشريط.

رجوع إلى changelist_view:

# admin.py
من django.db.models.functions استيراد Trunc
من django.db.models استيراد DateTimeField
SalesSummaryAdmin (ModelAdmin) الفئة:
    ...
    def changelist_view (self، request، extra_context = None):
        ...
        Summary_over_time = qs.annotate (
            فترة = TRUNC (
                'خلقت'،
                'يوم'،
                output_field = DateTimeField ()
            )،
        ) .values ​​( 'الفترة')
        .annotate (المجموع = مجموع ( 'سعر'))
        .order_by ( 'الفترة')
        summary_range = summary_over_time.aggregate (
            منخفض = إماراتي ( "مجموع")،
            ارتفاع = ماكس ( 'مجموع')،
        )
        high = summary_range.get ('high'، 0)
        low = summary_range.get ('low'، 0)
        response.context_data ['summary_over_time'] = [{
            "الفترة": س ['الفترة'] ،
            "الإجمالي": x ['total'] أو 0 ،
            'pct': \
               ((س ['الإجمالي'] أو 0) - منخفض) / (مرتفع منخفض) * 100
               إذا كانت مرتفعة> منخفضة أخرى 0 ،
        } لـ x في summary_over_time]
استجابة عودة

دعنا نضيف المخطط الشريطي للقالب ونضعه قليلاً:

    ...
    

المبيعات بمرور الوقت

    <نمط>
    .شريط الرسم البياني {
      عرض: المرن.
      تبرير المحتوى: الفضاء حولها ؛
      الارتفاع: 160 بكسل ؛
      الحشو أعلى: 60px.
      إخفاء الفائض؛
    }
    .bar-chart .bar {
        المرن: 100 ٪ ؛
        محاذاة الذات: نهاية المرن.
        الهامش الأيمن: 2 بكسل ؛
        الموقف: نسبي
        لون الخلفية: # 79aec8 ؛
    }
    .bar-chart .bar: آخر طفل {
        الهامش: 0
    }
    .bar-chart .bar: hover {
        لون الخلفية: # 417690 ؛
    }
    .bar-chart .bar .bar-tooltip {
        الموقف: نسبي
        مؤشر z: 999 ؛
    }
    .bar-chart .bar .bar-tooltip {
        الموقف: مطلق ؛
        أعلى: -60 بكسل ؛
        اليسار: 50 ٪ ؛
        تحويل: translateX (-50 ٪) ؛
        محاذاة النص: المركز ؛
        وزن الخط: غامق ؛
        التعتيم: 0؛
    }
    .bar-chart .bar: hover .bar-tooltip {
        التعتيم: 1 ؛
    }
    
    
    
        
        {٪ for x in summary_over_time٪}             
                
                    {{x.total | الافتراضي: 0 | intcomma}}
                    {{x.period | تاريخ: "د / م / Y"}}                                       {٪ endfor٪}                   

بالنسبة لأولئك الذين ليسوا على دراية بـ flexbox ، تعني هذه القطعة من CSS "السحب من الأسفل للأعلى والسحب إلى اليسار وضبط العرض ليناسب".

هكذا تبدو الآن:

يبدو ذلك جيدًا ، لكن ...

يمثل كل شريط في المخطط يومًا. ماذا سيحدث عندما نحاول عرض البيانات ليوم واحد؟ أو عدة سنوات؟

مخطط مثل هذا غير قابل للقراءة وخطير. يؤدي إحضار الكثير من البيانات إلى إغراق الخادم وإنشاء ملف HTML ضخم.

يحتوي Django Admin على تسلسل هرمي للتاريخ - دعونا نرى ما إذا كان يمكننا استخدام ذلك لضبط فترة الأشرطة استنادًا إلى التسلسل الهرمي للتاريخ المحدد:

def get_next_in_date_hierarchy (request، date_hierarchy):
    إذا كانت date_hierarchy + "__day" في request.GET:
        إرجاع "ساعة"
    إذا كان date_hierarchy + "__month" في request.GET:
        عودة "اليوم"
    إذا كانت date_hierarchy + "__year" في request.GET:
        إرجاع "الأسبوع"
    إرجاع "الشهر"
  • إذا قام المستخدم بتصفية يوم واحد ، فسيكون كل شريط ساعة واحدة (بحد أقصى 24 بار).
  • إذا اختار المستخدم شهرًا ، فسيكون كل شريط يومًا واحدًا (بحد أقصى 31 بار).
  • إذا اختار المستخدم في السنة ، فسيكون كل شريط أسبوعًا واحدًا (بحد أقصى 52 بار).
  • أكثر من ذلك وكل شريط سيكون شهر واحد.

نحتاج الآن إلى تعديل صغير واحد فقط لعرض قائمة التغيير:

SalesSummaryAdmin (ModelAdmin) الفئة:
    
   ...
    
    def changelist_view (self، request، extra_context = None):
        
       ...
        
        الفترة = get_next_in_date_hierarchy (
            طلب،
            self.date_hierarchy،
        )
        response.context_data ['الفترة'] = الفترة
        Summary_over_time = qs.annotate (
            فترة = TRUNC (
                'خلقت'،
                فترة،
                output_field = DateTimeField ()
            )،
        ) .values ​​( 'الفترة')
        .annotate (المجموع = مجموع ( 'سعر'))
        .order_by ( 'الفترة')
        
        ...

أصبحت وسيطة الفترة التي تم تمريرها إلى Trunc معلمة الآن.

النتيجة:

هذا اتجاه جميل ...

أين يمكن أن نأخذه من هنا؟

الآن وقد أصبح لديك كل وقت الفراغ هذا من عدم دحرجة لوحة القيادة الخاصة بك ، يمكنك:

  • اجعله اسرع.
  • إضافة زر.
الإجمالي {{summary_total.total | intcomma}} {{summary_total.total_sales | الافتراضي: 0}} $ 100٪