简体   繁体   中英

Add custom fields in Django admin for every model

I'm working on my personal website and wanted to handle both frontend and database translations (ie having everything in 2-3 languages). While there are already quite a few third-party apps for handling translation in the database, I thought it'd be both fun and interesting to build my own.


I'm at the last step. So far, everything's been working fine. Here's a quick recap on how it works (skipping over a few things):

  • I have a "translation" app in my django project
  • It provides a few different tables, most notably :
    • Language: list of available languages
    • Item: equivalent to saying "This is the 'name' field for the instance 33 of the 'Job' model"
    • Translation: a ManyToMany between Languages and Items. Basically all the translations for a given item.
  • This app provides an abstract model called "Translation model"
  • Every model from your regular apps that have fields to be translated must inherit from this model
  • Through signals and FK relationship, the app then handles both the creation and deletion of entries in the Item and Translation tables (automatically)

Ok so let's say we have a "Job" model that has 2 fields that must be translated: title and description . Now whenever I create a new entry in Job, here is what happens automatically:

  • 2 new entries are created in Item:
    • " Job n°X, name "
    • " Job n°X, description "
  • 4 entries are create in Translation :
    • " Job n°X, name, French "
    • " Job n°X, description, French "
    • " Job n°X, name, English "
    • " Job n°X, description English "

My issue is as follows:

  • I have many tables like "Job", that will trigger new entries in Item and then Translation
  • These tables all have ForeignKey towards the Item table, but Item does not have ForeignKey to these tables (since the same column stores data from different tables)

What I want to do is:

  • On the admin, when I go on a table like "Job", I want to see and directly update its translation texts (Translation.text). Which means seeing its 4 different "Translation" entries mentioned above. I already have a function that get the Translation instances, now it's more a problem of showing and editing them in the admin.
  • Rather than overriding manually each model/admin form, is there a way to apply this change globally.

My idea would be to "override the general change_form.html by adding a new zone dedicated to translation, which gets all the Translation entry related to an instance" (only if this instance is subject to translation). But not sure how to do that.

(Note that I already automatically detect all the models that require translations, and can easily get their specific fields)

Any help would be appreciated :)

For anyone that stumbles upon this topic, I've managed to solve this issue and publish my own application for database translation. The code is fully open-source and available here: https://pypi.org/project/django-database-translation/

As for the problem mentioned in my original post, I managed to apply the following logic:

  • I created a ModelForm that dynamically generates the "Fields to translate" for an item
  • I created a ModelAdmin that uses this forms and generates the same fieldnames as the above form
  • As a result, if I go on my "Job" item in the admin, I have access to all of its translation and can directly change them from this page

Here are the snippets, also available on https://github.com/Jordan-Kowal/django_database_translation

class DynamicTranslationForm(forms.ModelForm):
    """
    Form to use in a ModelAdmin for any model that has fields to translate.
    It will allow you to display and edit the Translation instances linked to the object.
    Since fields are dynamically generated, you must override the get_fieldsets method in the admin (or else they won't show)
    The "TranslatedAdmin" ModelAdmin natively use this form.
    """

    # ----------------------------------------
    # Core Methods
    # ----------------------------------------
    def __init__(self, *args, **kwargs):
        """Overridden method to dynamically add a new field for each Translation linked with our object"""
        super(DynamicTranslationForm, self).__init__(*args, **kwargs)
        if self.instance.pk:
            self.set_translation_info()
            for translation in self.translations:
                self.fields[translation["fieldname"]] = translation["field"]
                self.initial[translation["fieldname"]] = translation["instance"].text

    def save(self, commit=True):
        """Overridden method to save the updated Translation texts"""
        if self.instance.pk:
            for translation in self.translations:
                obj = translation["instance"]
                fieldname = translation["fieldname"]
                value = self.cleaned_data[fieldname]
                obj.text = value
                obj.save()
        return super(DynamicTranslationForm, self).save(commit=commit)

    # ----------------------------------------
    # Custom Methods
    # ----------------------------------------
    def set_translation_info(self):
        """
        Finds all the Translation instances linked to our object, and stores their info in an attribute
        The attribute is a list of dict, each dict containing the information of one translation
        """
        obj = self.instance
        information = []
        translations = obj.get_translations()
        for translation in translations:
            fieldname = create_translation_fieldname(translation)
            information.append({
                "instance": translation,
                "fieldname": fieldname,
                "field": forms.CharField(required=False, widget=forms.Textarea)
            })
        self.translations = information
# ADMIN.PY
class TranslatedAdmin(admin.ModelAdmin):
    """
    ModelAdmin to use as parent for any model that has fields to translate
    It comes with the "DynamicTranslationForm" and custom methods to display its fields
    """

    # ----------------------------------------
    # Config
    # ----------------------------------------
    form = DynamicTranslationForm

    # ----------------------------------------
    # Detail View
    # ----------------------------------------
    fieldsets = []

    # ----------------------------------------
    # Custom Methods
    # ----------------------------------------
    def get_form(self, request, obj=None, **kwargs):
        """Required for get_fieldsets"""
        kwargs['fields'] = flatten_fieldsets(self.fieldsets)
        return super().get_form(request, obj, **kwargs)

    def get_fieldsets(self, request, obj=None):
        """
        Allows us to display the field dynamically created by "DynamicTranslationForm"
        The fieldnames in "DynamicTranslationForm" and this function must be identical
        In other words:
        - "DynamicTranslationForm" creates the fields
        - This function adds fields with the same name to the fieldsets
        - As a result, the fields now appear on the form
        """
        fieldsets = self.fieldsets.copy()
        # Get current Item
        url = request.build_absolute_uri("?")
        if url.endswith("/change/"):
            url = url[:-8]
            object_id = url.split("/")[-1]
            obj = self.model.objects.get(pk=object_id)
            # Create a field for each translation associated with our object
            fields = []
            translations = obj.get_translations()
            for translation in translations:
                fieldname = create_translation_fieldname(translation)
                fields.append(fieldname)
            # Add a fieldset with our fields
            fieldsets.append(['TRANSLATIONS', {'fields': fields}])
        return fieldsets

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM