Tuesday, August 7, 2018

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

Leave a Comment

(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.

2 Answers

Answers 1

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!

Answers 2

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.

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment