[Django]-Optimize multiple Views to one View – Django

1👍

Since you want "one url" and "single view", you can’t pass the relevant parameters in __init__.

To handle that:

  1. dal (django-autocomplete-light) supports forwarded values, so you can use that to specify which model you want.
  2. Use a static config with lambda functions to define the model, model_query and model_field_name.
  3. Override model and model_field_name fields as properties.

This would be how you can implement it:

class AutocompleteHandler(autocomplete.Select2QuerySetView):
    config = {
        'Location': {
            'model': Location,
        },
        'Society': {
            'model': Society,
            'model_query': lambda self: {'location_id__in': self.forwarded.get('preferred_area', None)},
        },
        'Property': {
            'model': Property,
            'model_query': lambda self: {'type__enq_code': self.request.session.get('enquiry_flag', 3)},
        },
        'Floor': {
            'model': Floor,
            'model_field_name': 'floor',
        },
        'Project': {
            'model': Project,
        },
    }

    def get_queryset(self):
        if not self.request.user.is_authenticated:
            return self.model.objects.none()

        qs = self.model.objects.filter(**self.model_query)
        qs = self.get_search_results(qs, self.q)
        return qs

    @property
    def model(self):
        return self.model_config['model']

    @property
    def model_config(self):
        model_name = self.forwarded.get('m')
        model_config = self.config.get(model_name)
        if not model_config:
            raise ValueError
        return model_config

    @property
    def model_field_name(self):
        return self.model_config.get('model_field_name', 'name')

    @property
    def model_query(self):
        model_query = self.model_config.get('model_query')
        if not model_query:
            return {}
        return model_query(self)

Usage:

from dal import autocomplete, forward

widgets = {
    'location': autocomplete.ModelSelect2(
        url='autocomplete-handler',
        forward=(forward.Const('Location', 'm'),),
    ),
}
👤aaron

1👍

class GenericModelAutoComplete(autocomplete.Select2QuerySetView):
    def get_queryset(self):
       if not seslf.request.user.is_authenticated:
            return self.model.objects.none()
        qs = self.qs or self.get_qs()
        return self._get_queryset().filter(name__istartswith=self.q)

class FloorAutocomplete(GenericModelAutoComplete):
    qs = Floor.objects.all()

class PropertyAutocomplete(GenericModelAutoComplete):
    def get_qs(self):
        enquiry_flag = self.request.session.get('enquiry_flag', 3)
        return PropertyCategory.objects.filter(type__enq_code = enquiry_flag)

And so on, and with the same urls. Optionally, there is this: https://github.com/isik-kaplan/django-urls

Which you can use to simplify how your urls work with your views.

from django_urls import UrlManager

autocomplete = UrlManager()

class GenericModelAutoComplete(autocomplete.Select2QuerySetView):
    def get_queryset(self):
       if not seslf.request.user.is_authenticated:
            return self.model.objects.none()
        qs = self.qs or self.get_qs()
        return self._get_queryset().filter(name__istartswith=self.q)

    def __init_subclass__(self, **kwargs):
        # Automatically adds the view to autocomplete urlpatterns
        cls = self.__class__
        autocomplete.path(name=f"{cls.endpoint}-autocomplete")(cls)
        return super().__init_subclass__(**kwargs)

class FloorAutocomplete(GenericModelAutoComplete):
    qs = Floor.objects.all()

class PropertyAutocomplete(GenericModelAutoComplete):
    def get_qs(self):
        enquiry_flag = self.request.session.get('enquiry_flag', 3)
        return PropertyCategory.objects.filter(type__enq_code = enquiry_flag)

# urls.py

from .views import autocomplete
urlpatterns = autocomplete.url_patterns

The second version is just 4-5 lines longer in the base view, but it saves you from writing a new url pattern for every view you have.

1👍

You were almost there. In Django, you can override class attributes by providing arguments for the as_view function, as explained in Django docs. To use that feature, you need to declare your parameters as class attributes instead of writing them in init:

class AutoCompleteHandler(autocomplete.Select2QuerySetView):
    model = None
    model_query = None


    def get_queryset(self):
        if not self.request.user.is_authenticated:
            return self.model.objects.none()
        enquiry_flag = self.request.session.get('enquiry_flag', 1)

        qs = self.model.objects.filter(**self.model_query)

        if self.q:
            qs = qs.filter(name__istartswith=self.q)

        return qs

Then you can use your view in your urlpatterns as such:

path('locations/', views.AutoCompleteHandler.as_view(model=Location, model_query={}), name='location-autocomplete'),

To add some more flexibility, you can define some getter functions for your parameters:

class AutoCompleteHandler(autocomplete.Select2QuerySetView):
    model = None
    model_query = None

    def get_model(self):
        if self.model is None:
            raise ImproperlyConfigured("Some useful exception")
        return self.model

    def get_model_query(self):
        model_query = {} if self.model_query is None else self.model_query
        return model_query

    def get_queryset(self):
        model = self.get_model()
        if not self.request.user.is_authenticated:
            return model.objects.none()
        enquiry_flag = self.request.session.get('enquiry_flag', 1)

        qs = model.objects.filter(**self.get_model_query())

        if self.q:
            qs = qs.filter(name__istartswith=self.q)

        return qs

Leave a comment