8

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

Gudarzi
  • 486
  • 3
  • 7
  • 22
aalberti333
  • 1,181
  • 1
  • 14
  • 24
  • 1
    Have you tried adding a method for filling that field (in models.py) and redefining save() to call it? – Gudarzi Jul 19 '18 at 20:20
  • Hey @Ehsan , thanks for the response. No, I haven't, I've only tried filling in this data from the `BookingsSerializer` and `BookingsView`. I'm still pretty new to DRF and Django in general, so I'm having a difficult time understanding where to implement my logic. Would you possibly be able to provide an example of how to implement that in the Model? – aalberti333 Jul 19 '18 at 20:32

3 Answers3

2

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.

Atul Mishra
  • 1,898
  • 2
  • 13
  • 18
0

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!

Gudarzi
  • 486
  • 3
  • 7
  • 22
  • Thanks for the update @Ehsan , but I want the function output displayed before saving the data. Basically, if I open the table in the browsable api, I want the function output to populate `calendarydays` as a list for the user to choose from, based off of the information already contained in `trainer_availability_only` – aalberti333 Jul 31 '18 at 17:09
  • @aalberti333 Well, information inside those two fields doesn't exist until the user saves it. For your case I suggest instead of doing this "populate a list based on information contained in another field" , moving this logic to the frontend (it's so much easier there). There must be ways to do this using just django, but it's not pretty and nice. – Gudarzi Jul 31 '18 at 19:05
0

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.

Henry Woody
  • 14,024
  • 7
  • 39
  • 56
  • He is not using the view. He is using DRF browsable API. I think he just needs to prefill the form with the data he wants, but maybe I didnt understand correctly. http://www.django-rest-framework.org/api-guide/renderers/#browsableapirenderer – wdfc Aug 01 '18 at 11:23
  • Thank you for the response, but @wdfc is correct. I have a `views.py` file, but I'm using the DRF browsable API. I want to prefill a field containing the datetime output created by my function. So when a user goes to the `Bookings` table they can select one of those options inside `calendarydays`. My thought was, if I can pull data from `trainer_availability_only` I should be able to take that, manipulate it, and post those manipulations in a separate field. Basically, if the data is already there to work with, I figured I could fill a different field with datetime data which depends on it. – aalberti333 Aug 01 '18 at 13:07
  • I believe you can do what you want by customizing the form for this model in the browsable api view. Have you tried the above link I posted? Unfortunately I have to work now, but if this question is still open later on, I can try and customize the form and post an example. As a side note, instead of storing a charfield, you can store it as a jsonfield, which might be easier to deal with! – wdfc Aug 01 '18 at 14:04
  • @wdfc That would be incredible, thanks! I'll go ahead and change it to a jsonfield. I checked the link, but I'm not sure how I would have to change things around to create what I need to. The only example provided was pretty basic. – aalberti333 Aug 01 '18 at 18:03