django-ledger icon indicating copy to clipboard operation
django-ledger copied to clipboard

Using BaseClass for Views / Forms / Models

Open NadjibR opened this issue 3 years ago • 2 comments

Is your feature request related to a problem? Please describe. No. By checking out the code i've noticed that most of the classes use common fields, properties, contexts

Describe the solution you'd like I'm workig on a similar project (it's like mini erp system) and I try to use shred base classes as much as I can, to avoid make repeated contexts in views, declare the same field in all models (like id, created_at, create_by, modified_at_modified_by, ...)...

Additional context Example

A Base Class Model :

class BaseModel(models.Model):
    id = models.AutoField(_('Id'), primary_key=True)
    active = models.BooleanField(_('Active'), default=True, blank=True, null=True)
    created_at = models.DateField( _('Created at'), auto_now_add=True, blank=True, null=True)
    updated_at = models.DateField( _('Updated at'), auto_now=True, blank=True, null=True)
    created_by = models.ForeignKey('base.User', on_delete=models.PROTECT, related_name='%(class)s_created_by', verbose_name=_('Created by'))
    updated_by = models.ForeignKey('base.User', on_delete=models.PROTECT, related_name='%(class)s_updated_by', verbose_name=_('Updated by'))
    base_company = models.ForeignKey('base.Company', on_delete=models.PROTECT, blank=True, null=True, verbose_name=_('Company'), related_name="%(class)s_company")

    class Meta:
        ordering = ['pk']
        abstract = True

Item Model inherits BaseModel :

class Item(BaseModel):
    ...
    class Meta:
      verbose_name = _("Item")
      verbose_name_plural = _("Items")

A Base View regroups all the shared contexts and the template :

class BaseListView(ListView):
    template_name = 'base/list.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        object_name = self.model._meta.object_name
        verbose_name = self.model._meta.verbose_name
        verbose_name_plural = self.model._meta.verbose_name_plural
        context['page_title'] = _('{}'.format(verbose_name_plural))
        context["btn_create_title"] = _('New {}'.format(verbose_name))
        context["btn_update_title"] = _('Update {}'.format(verbose_name))
        context['btn_detail_url'] = '{}:detail'.format(object_name)
        context['add_url'] = reverse_lazy('{}:add'.format(object_name))
        context['edit_url'] = '{}:edit'.format(object_name)
        return context

    def get_queryset(self):
        queryset = self.model.objects.filter()
        return queryset

    class Meta:
        abstract = True

And since all list pages are rendred the same way, (A table with some action buttons) why not using the same template ?

You see that what evev the self.object is in ModelView, it will generate page title, action buttons' titles (Create new ..., Update ..., Delete ...) the same way. What happens if you guys decide to use "Edit .." for the edit button rather than "Update ...", you have to change this in all the Views.

Same things for CreateView, UpdateView, they use the same template (it's a little bit tricky, but I made to work properly)

This way, I can create a new Model -> View -> Form and make it works (List of objects, Create, Update & Delete Object) in few minutes

I'd love to help if I can, let me know what you think.

NadjibR avatar Feb 17 '22 10:02 NadjibR

And this how my shared CreateView looks like :

class BaseCreateView(CreateView):
    template_name = 'stock/item-form.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        verbose_name = self.model._meta.verbose_name
        context["page_title"] = _('Create new {}'.format(verbose_name))
        context["action_type"] = "create"
        return context

    def form_valid(self, form):
        form.instance.created_by = self.request.user
        form.instance.updated_by = self.request.user
        form.instance.created_at = timezone.now()
        form.instance.updated_at = timezone.now()
        form.instance.base_company = self.request.user.current_company
        return super().form_valid(form)

    def get_success_url(self):
        return reverse_lazy('{}:detail'.format(self.model._meta.object_name), kwargs={'pk': self.object.pk})

    class Meta:
        abstract = True

NadjibR avatar Feb 17 '22 10:02 NadjibR

I agree with your approach. Django Ledger makes use of MixIns to encapsulate common behavior among views and avoid repetition. Can you suggest changes to the Django Ledger code to accomplish what you are proposing?

Thanks

elarroba avatar Mar 05 '22 23:03 elarroba

The code has been refactored since version 0.4 to incorporate good practices like the one described on this issue.

elarroba avatar Jun 22 '23 20:06 elarroba