Search code examples
djangodjango-forms

Django Form inital values don't work on render


I have a model `Transaction' and ModelForm to create one :

class Transaction(models.Model):
    """ Invoice model.
     Represents a basic income/outcome transaction. """
    
    title = models.CharField(max_length=32, verbose_name="Title")
    category = models.ForeignKey(Category, related_name="transactions", on_delete=models.CASCADE, null=True, blank=True)
    operation = models.CharField(max_length=8, choices=OPERATION_TYPE, verbose_name="operation")
    value = models.DecimalField(max_digits=14, decimal_places=2, verbose_name="value")
    currency = models.CharField(max_length=3, choices=CURRENCY_CHOICE, verbose_name="currency")
    comment = models.CharField(max_length=255, null=True, blank=True)
    date_created = models.DateField(auto_now_add=True, blank=True, null=True)

    class Meta:
        verbose_name_plural = "Transactions"
        ordering = ["-date_created"]

class NewInvoiceForm(forms.ModelForm):
    category = TreeNodeChoiceField(queryset=Category.objects.all())

    class Meta:
        model = Transaction
        fields = ("title", "category", "operation", "value", "currency")
        help_texts = {
            "operation": _("Money spent/received. For expenses use a negative value.")
        }

I have a Preference model linked with each user to store settings :

class Config(models.Model):
    """ Represents user's preferences. """
    
    user = models.OneToOneField(CustomUser, related_name="config", on_delete=models.CASCADE)
    currency = models.CharField(max_length=3, choices=CURRENCY_CHOICE, default=DEFAULT_SETTINGS["currency"])
    language = models.CharField(max_length=56, choices=LANGUAGE_CHOICE, default=DEFAULT_SETTINGS["language"])

    class Meta:
        verbose_name = "config"
        verbose_name_plural = "configs"

I'm trying to get user's currency from Config model as initial value for Transaction creation form. But initial value never renders.

My view :

def create_transaction_view(request):
    if request.method == "POST":
        form = NewInvoiceForm(
            request.POST,
            instance=request.user
        )
        if form.is_valid():
            new_invoice = Transaction(**form.cleaned_data)
            # retrieving user's currency preference for further converting if income is different from base currency
            user_currency = request.user.config.currency
            # performing currency conversion to maintain consistent data for statistic and analyse
            if user_currency != new_invoice.currency:
                transaction_converted_value = CurrencyExchangeManager.currency_converter(
                    value=new_invoice.value,
                    exchange_to=user_currency,
                    base=new_invoice.currency
                )
                new_invoice.value = transaction_converted_value
                new_invoice.currency = user_currency
            new_invoice.save()
        return HttpResponseRedirect(request.META.get('HTTP_REFERER', "/"))

I always get a 'dash' field as initial choice. Appreciate any suggestions on this topics.


Solution

  • Your code is mostly correct, but you need to check if it's handling both GET and POST requests appropriately for your form.

    Here request.method == "POST" condition is crucial for processing form submissions, where you handle the data sent by the user. This block is where the form's data is validated and processed.

    But when the user submits the form, this block takes charge, processing the submitted data, validating it, and then performing necessary operations like saving the data or doing additional processing, such as currency conversion in your case. Till this point your code was correct.

    But for initial form rendering, particularly for GET requests, the form should be set up with initial values which is missing from your code.

    When the form is first requested by the user, it's typically a GET request. In this scenario, you should provide initial values for the form fields. In your application, this means setting the initial value of the currency field to the user's preferred currency. You can do this by using the the else block of your view function, which handles the GET request like this:

    else:
        user_currency = request.user.config.currency
        form = NewInvoiceForm(initial={'currency': user_currency})
    

    The form is instantiated with the initial value for the currency, ensuring that when the user accesses the form to create a new transaction, they see their preferred currency pre-selected.

    Here is the full update code that you need in views.py for create_transaction_view method

    def create_transaction_view(request):
        if request.method == "POST":
            form = NewInvoiceForm(
                request.POST,
                instance=request.user
            )
            if form.is_valid():
                new_invoice = Transaction(**form.cleaned_data)
                # retrieving user's currency preference for further converting if income is different from base currency
                user_currency = request.user.config.currency
                # performing currency conversion to maintain consistent data for statistic and analyse
                if user_currency != new_invoice.currency:
                    transaction_converted_value = CurrencyExchangeManager.currency_converter(
                        value=new_invoice.value,
                        exchange_to=user_currency,
                        base=new_invoice.currency
                    )
                    new_invoice.value = transaction_converted_value
                    new_invoice.currency = user_currency
                new_invoice.save()
         else:
              user_currency = request.user.config.currency
              form = NewInvoiceForm(initial={'currency': user_currency})
    
         return HttpResponseRedirect(request.META.get('HTTP_REFERER', "/"))
    

    Note: You can even optimize the code a little by initializing this user_currency = request.user.config.currency at the beginning of the create_transaction_view function so we don't need to repeat this twice in the code. I gave it like that for your understanding.