aalberti333
aalberti333

Reputation: 1229

Django Rest Framework - passing Model data through a function, then posting output in a separate field in the same model

(Django 2.0, Python 3.6, Django Rest Framework 3.8)

I'm trying to fill the calendarydays field in the model below:

Model

class Bookings(models.Model):
    booked_trainer = models.ForeignKey(TrainerProfile, on_delete=models.CASCADE)
    booked_client = models.ForeignKey(ClientProfile, on_delete=models.CASCADE)
    trainer_availability_only = models.ForeignKey(Availability, on_delete=models.CASCADE)
    calendarydays = models.CharField(max_length=300, blank=True, null=True)

    PENDING = 'PENDING'
    CONFIRMED = 'CONFIRMED'
    CANCELED = 'CANCELED'

    STATUS_CHOICES = (
        (PENDING, 'Pending'),
        (CONFIRMED, 'Confirmed'),
        (CANCELED, 'Canceled')
    )


    booked_status = models.CharField(
        max_length = 9,
        choices = STATUS_CHOICES,
        default = 'Pending'
    )

    def __str__(self):
        return str(self.trainer_availability_only)

Now, I have a function that takes values from trainer_availability_only and converts those values to a list of datetime strings, the returned output would look like this:

{'calendarydays': ['2018-07-23 01:00:00', '2018-07-23 02:00:00', '2018-07-23 03:00:00', '2018-07-30 01:00:00', '2018-07-30 02:00:00', '2018-07-30 03:00:00', '2018-08-06 01:00:00', '2018-08-06 02:00:00', '2018-08-06 03:00:00', '2018-08-13 01:00:00', '2018-08-13 02:00:00', '2018-08-13 03:00:00', '2018-08-20 01:00:00', '2018-08-20 02:00:00', '2018-08-20 03:00:00']}

Problem

How can I fill the calendarydays field with the function output for a user to select from a dropdown, and where should I implement this logic (in my view or the serializer)? My main point of confusion is that, because my function depends on data from trainer_availability_only, I don't want to create a separate model/table for this information (as that would seem too repetitive). I also don't fully understand where in my serializers or views I can implement some sort of dropdown for a User to choose a single calendarydays value for (like I would be able to for a ForeignKey or OneToOneField for example).

Details for the other models aren't really relevant to the question, except trainer_availability_only, which basically gives the user a dropdown selection that would look like this:

('Monday','12:00 am - 1:00 am')
('Wednesday','4:00 pm - 5:00 pm')
etc.

Any help is greatly appreciated.

Upvotes: 8

Views: 1546

Answers (3)

Atul Mishra
Atul Mishra

Reputation: 2018

Assuming that you will not be using this API through browsable API and some client will consume these API's, you should give a separate endpoint to fetch the options which can be shown in the dropdown.

Since the calendar days only depends on the selected Availability, the endpoint can be something like /availability/<:id>/calender-days, where <:id> is the id of the selected option. This will be a GET request with a separate view where you can call your function which calculates calendar days from Availability object and return the list of calendar days. This endpoint will be useful in both cases when you create a new Booking object or you update that.

On your point of showing in the browsable API form, browsable API forms don't load dynamic fields, so it will not call your logic to fetch calendar days based on the selection. For the Foriegn key and one-to-one relation, it already loads the queryset of the models while rendering the form and uses those to show the dropdowns. But if your actual use case is to consume API's using browsable API form then you will have to write some custom javascript to load the values.

Upvotes: 2

Henry Woody
Henry Woody

Reputation: 15672

Unfortunately, with the built-in template engine for Django you cannot dynamically rerender parts of a template based on something from the backend. As I see it, you have two options:

  1. Request new options via an API in your application and update the HTML using JavaScript; or
  2. Generate all possible sets of options for the initial request and send them all at once and then just filter in the template

Option 1: in the template

First you would need to make an API endpoint for the Availability model. It can be as simple as a single url route with a pattern like '/availability/(?P<pk>[0-9]+)$' (ex: '/availability/5'). This route should return as a JSON object the Availability object with pk=pk (5 in the example above). You can JSON serialize the object yourself if it is fairly simple, just convert it to a list or dictionary that contains simple Python types (e.g. str, int, bool, list, dict). If this is going to be a larger application with more involved models or a few more API routes, it's worth checking out Django Rest Framework for building your API.

With that route working, you can call it with JavaScript from your template. You'll likely want to add an eventListener on change to the <select> or <input> for trainer_availability_only. So if the user changes the value of trainer_availability_only, your template with make a request to your API route for the Availability object specified in the form.

With the options returned by your API (calOptions in the example below), you can populate the <option>s in the <select> for calendarydays. You can do something like

const calSelect = document.querySelector('#select-calendarydays');
calSelect.options = []; // to clear the old options
calOptions.forEach(calOption => {
    const optionElement = document.createElement('option');
    optionElement.text = calOption.text;
    optionElement.value = calOption.value;
    calSelect.add(optionElement);
}

The text attribute is what displays to the user and the value attribute is what actually gets submitted. Note that the above supposes that calOptions is something of the form:

[
    {'text': 'Monday 12:00 am - 1:00 am', 'value': '2018-07-23 00:00:00'},
    {'text': 'Tuesday 8:00 am - 9:00 am', 'value': '2018-07-24 08:00:00'}
]

And that'll do it for this route.

Option 2: in the view

You can also do this in the view, but it isn't particularly scalable. What you would do is load all of the Availability objects in the view and then pass a dictionary containing sets of options (presumably one set per Availability object) and pass that to the template via the context argument.

That would look something like this:

option_sets = {}
for availability in Availability.objects.all():
    option_sets[availability.pk] = availability.get_calendarydays_options()

...

context = {'option_sets': option_sets}
return render(request, 'my_app/form-page.html', context)

Where availability.get_calendarydays_options returns a list of options like calOptions in the previous option.

Then in the template you need to get the option_set from option_sets that is for the selected Availability object. Do do that see this answer: Django template how to look up a dictionary value with a variable because dictionary access doesn't work the same in the template engine as it does in Python. For populating the <option>s in the <select>, you can do something like:

<select id='select-calendarydays>'
    {% for cal_option in option_set %}
        <option value="{{ cal_option.value }}">{{ cal_option.text }}</option>
    {% endfor %}
</select>

Why I say this is not very scalable is because it requires sending all of the possible sets of options for calendarydays in the initial response. I recommend using Option 1, but the choice is yours.

This seems like a complex problem so I doubt this response will answer everything, but hopefully it sets you in the right direction a bit. Let me know if you have questions.

Upvotes: 0

Gudarzi
Gudarzi

Reputation: 575

Well I don't have the exact answer for your question since I don't understand what you're trying to do, but this is what I can tell you about implementing logic on models:

Assuming you have this model:

class MyModel(models.Model):
    field1=...
    field2=...

    def foo(self):
        ### Put your logic here ###
        self.field1 = self.field2 + 5

    def save(self, *args, **kwargs):
        self.foo()
        return super(MyModel, self).save(*args, **kwargs)   

This method runs whenever your data gets saved (or updated I assume). Don't forget to validate your data before saving and don't put your logic on views or serializers, those are not good ideas. You can also create another module for your logic, but that's more complicated!

Upvotes: 0

Related Questions