🇪🇦 Leer en Español, “CodenerixModel

In order for any Django project to support CODENERIX properly, it is necessary to take some important steps to ensure a good start to the project. We are not going to repeat that the easiest way to get started is by using the CODENERIX Examples available on GITHUB.

That is why this time we are going to focus on the CORE / KERNEL of CODENERIX and that we will know from now on as CodenerixModel.

CodenerixModel is an extended class from Django’s models.Model where a series of new functionalities are contemplated that we will explain below. First of all, we must bear in mind that most of the functionalities that we will see in CODENERIX are achieved through the use of direct inheritance of classes of this framework. To do this, we are going to see a simple example of a class developed with CODENERIX:

from codenerix.models import CodenerixModel

from django.db import models

class Author(CodenerixModel):
    name = models.CharField(_(u'Name'), max_length=128, blank=False, null=False)
    birth_date = models.CharField(_(u'Birth Date'), max_length=128, blank=False, null=False)
    def __fields__(self, info):
        fields=[]
        fields.append(('name', _('Name'), 100, 'left'))
        fields.append(('birth_date', _('Birth Date')))
        return fields

In this example we see several things that stand out to us compared to a normal declaration of a model in Django:

  1. We import CodenerixModel with:
    from codenerix.models import CodenerixModel

  2. The model inherits CodenerixModel (not from models.Model):
    class Author(CoderixModel):

  3. There is a special method that is required called __fields__ and takes an “info” argument:
    def fields(self, info):

    We must remember that this method must be defined in all the classes that inherit from CodenerixModel since it is used by CODENERIX to know how to show the information to the end-user of the application in such a way that it is clear, simple, and usable.
  4. The rest of the information in the class is simple to understand and responds to the general paradigm that Django uses. In fact, CODENERIX always inherits Django and passes its management to it, thus always ensuring that any improvements to Django are also available in CODENERIX.

What does the __fields__ method contain?

It collects what information from the model is useful to be shown when the user wants to work with that model. This method is usually called from lists since the fields that appear here will be used as columns in the listing.

def __fields__(self, info):
    fields=[]
    fields.append(('name', ('Name'), 100, 'left')) fields.append(('birth_date', ('Birth Date')))
    return fields

⚠ IMPORTANT: This method is mandatory in all models that inherit from CodenerixModel.

The method receives an “info” argument that contains information about the query received (request) including, among other data, the user who makes it and other important meta information for decision making that will be made within __fields__. This example is linear, but we can take advantage of this information to detect a user’s level of access and display some fields or others in the list, adapting the content of the list depending on the user or the system conditions.

This method is expected to respond with a list of tuples that must contain at least 2 elements, the first will be the name of the field according to the Django‘s model, and the second the translatable name of the field to be shown to the user in the title of the column referenced by that field.

Each tuple, therefore, consists of 2 mandatory elements and several optional elements:

  • Required: Model field name
  • Required: Translatable name of the field to be displayed to the user (it can be None to prevent Django from rendering that column, so we can receive this field in the JSON response of the list but without being displayed visually. It is very useful when you want to compose several fields into one)
  • Optional: Length of field width in pixels (deprecated)
  • Optional: Alignment of the text within the field (Ex: ‘center‘)
  • Optional: Name of the filter to apply to the field (Ex: ‘skype‘ which will add the necessary information so that the output of the field is a clickable phone number)

There are 3 other special methods that provide different functionalities to the models, these are __limitQ__, __searchF__, and __searchQ__.

What is the __limitQ__ method?

It is a limiting method, its objective is to limit the results to which the user has access. In this way, __limitQ__ allows you to control the result returned to the user based on the query environment, in such a way that if you are a user without privileges, you will not be able to see certain records because a filter will be applied. It is very useful when working with Roles since it allows you to control the records that the user sees based on the role they have.

def __limitQ__(self, info):
    limits = {}
    if not info.user.is_admin:
        criterias = []
        criterias.append(Q(model__pk=pk_condition))
        criterias.append(Q(model__field1=condition))
        limits['profile_people_limit'] = reduce(operator.or_, criterias)
    return limits

The method expects to return a dictionary where the key simply indicates the line to operate on (in case an error occurs while processing the lookup) and the value on the other hand is a Django‘s QObject. The QObject will be injected in the Queryset of the list when it is requested, applying or not the filter depending on the query.

What is the __searchQ__ method?

It is the method used to filter the queries with the text that is entered in the upper box of the listings. In this way, everything that the user writes in the search box will arrive in the “text” variable and this will allow filtering of the search with this text.

def __searchQ__(self, info, text):
    text_filters = {}
    text_filters['identifier1'] = Q(CharField1__icontains=text)
    text_filters['identifier2'] = Q(TextField1__icontains=text)
    text_filters['identifier3'] = Q(IntegerField=34)

    #If text have this a especific word can return another Q condition
    if text.find(u'magic') != -1:
        text_filters['identifier4'] = Q(identifier=34)

    return text_filters

The method expects to return a dictionary where the key simply indicates the line to operate on (in case an error occurs while processing the lookup) and the value on the other hand is a Django‘s QObject. The QObject will be injected in the Queryset of the list when it is requested, applying or not the filter depending on the query.

What is the __searchF__ method?

It is in charge of displaying the search filters and is used when we want filters to be displayed that facilitate the search. Specifically, its use refers to the personalized definition of these filters, for example, a selector that shows “A”, “M” and “S”, in such a way that if you press “A” you will search for everything that begins with “A”, idem for M and S, but you will not be able to search for other letters. It is very useful to build combined searches, for example: “give me those of type A” (which refers to green and purple together). It is also used to generate complex filters. CODENERIX automatically includes filters in all the fields of a list with which it knows how to operate natively, so filters will appear for text fields, numeric fields, BooleanFields, ChoiceFields, and dates.

def __searchF__(self, info):
    list1 = []
    for l1 in Model.objects.all():
        list1.append((l1.id, l1.field1 + ' ' + l1.field2))

    list2 = []
    for li in Model2.objects.all():
        list2.append((li.id, str(li.field)))

    text_filters = {}
    text_filters['field1'] = (_('Field1'), lambda x: Q(field1__startswith=x), [('h', _('Starts with h')), ('S', _('Starts with S'))])
    text_filters['field2'] = (_('Field2'), lambda x: Q(field2__pk=x), list1)
    text_filters['external'] = (_('Field3'), lambda x: Q(pk=x),list2)

    return text_filters

The method expects to return a dictionary where the key of the dictionary indicates the name of the filter, if the name of the filter matches a field in the list, then the filter will be shown in the hidden row below the title of the list and that is displayed with the icon ““, otherwise it will be displayed as an external filter.

The value of the key must be a tuple with 3 elements:

(('Field1'), lambda x: Q(field1__startswith=x), [('h', ('Starts with h')), ('S', _('Starts with S'))])

…in such a way that the first element will be the name of the filter in translatable format, the second a lambda function that receives the value of the filter and returns a QObject that helps the filter to perform as expected, and the third element should be a list of options to display in the filter. The QObject returned by the lambda function will be injected into the Queryset of the list when it is requested, applying or not the filter depending on the query.

How do CodenerixModel and GenList interact?

Although later we will talk about GenList (view used to build lists) we must say that __fields__, __limitQ__, __searchQ__ and __searchF__ are methods that can appear indistinctly in the model and in the view of a list. Here’s how filters interact when a model specifies a filter and a listing doesn’t, or vice versa.

The following 3 tables show 3 columns:

  • The first indicates if the method is defined in CodenerixModel.
  • The second indicates if it is defined in GenList.
  • The third column indicates the result of applying these filters.

__fields__
CodenerixModelGenListApplicable
NONO— ERROR —
NOYES— ERROR —
YESNOCodenerixModel
YESYESGenList

__limitQ__
CodenerixModelGenListApplicable
NONONo filter is applied
NOYESGenList filter is applied
YESNOCodenerixModel filter is applied
YESYESGenList and CodenerixModel filters
are applied to put them together
with the logic operator “AND“.

__searchQ__ & __searchF__
CodenerixModelGenListApplicable
NONONo filter is applied
NOYESGenList filter is applied
YESNOCodenerixModel filter is applied
YESYESGenList filter is applied

We will remember these tables again later when I explain GenList in detail.

And what else?

In addition to the previous methods, we must point out that CodenerixModel adds a series of extra functionalities to the standard Django models.

  1. By default, it adds the created and updated fields that are automatically managed by CODENERIX to remember when the record was created and when it was last edited.
  2. It allows the use of a new “Meta” class (equivalent to Django’s) called “CodenerixMeta“.
  3. The standard permissions are replaced by: add, change, delete, view and list. The new types are “view” and “list” to define if the record can be displayed or listed.
  4. Models also have the ability to use the lock_update and lock_delete methods to stop a record update or delete. An example of this is that there is a record that depends on a specific circumstance of the stored data. Thanks to these methods, records can be capriciously blocked in a list and the user is properly informed of the reason for said blocking.

    def lock_delete(self): if self.documents.exists(): return ("The note cannot be deleted, there is a document that blocks it") elif self.people.exists(): return ("The note cannot be deleted, there are people still working with it") else: return super(Point, self).lock_delete()

  5. In this example, the deletion of a note is prevented when it has associated documents or when there are people using it, otherwise, the responsibility is delegated to the parent class.
  6. When a record is going to be deleted, CodenerixModel analyzes the relationships with other models and their deletion protections to respond in a “polite” way in case of trying to delete a record that by model definition (models.PROTECT) cannot be deleted.
  7. All classes that inherit from CodenerixModel can also inherit from GenLog. When doing so, CODENERIX will record in a log any operation that occurs in a record of that class.

Now we are ready to learn how to use GenList.