ManyToMany model field where choices change based on boolean - django

I'm relatively new to Django, and I've been trying to find a way to implement a ManyToMany field whose visible 'choices' within the UI change based upon a BooleanField found within the same model.
For instance, suppose I have a model that represents different jobs, and a worker model that has a manytomany relationship to this jobs model. Suppose also that there are two types of workers: a manager and non-manager which is represented as a BooleanField. If you are a manager, you have certain jobs that a worker does not have and vice versa.
I'm trying to find a way, without creating a new table, to have it such that the jobs listed within the manytomany relationship are dependent on the boolean value of 'is_manager'. That is, if you were to click 'is_manager', this should list manager-specific jobs, yet these manager specific jobs live within the same table as non-manager jobs -- those would just be blank.
I've been looking into the through field, etc, but all solutions that I come up with seem to be dependent on making another table. I'm sure there is a way to do this better though.
Thank you.

I suggest this approach:
class MyModelForm(forms.ModelForm):
class Meta:
model = MyModel
fields = ['jobs', 'username']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
instance = kwargs.get('instance', None)
if instance is not None:
if instance.is_manager:
self.fields['jobs'].queryset = Jobs.objects.filter(manager=True)
else:
self.fields['jobs'].queryset = Jobs.objects.filter(manager=False)

Related

Enforce or test on_delete behavior of all ForeignKey fields using a specific model

Let's say I have a proxy user model as
class UserWithProfile(User):
profile_description = models.TextField()
class Meta:
proxy = True
ordering = ('first_name', )
I want to make certain that all data which could in the future be associated with a UserWithProfile entry is deleted when this profile is deleted. In other words I want to guarantee the on_delete behavior of all existing and future ForeignKey fields referencing this model.
How would one implement either a test checking this, or raise an error when another on_delete behavior is implemented?
I know it would be possible to make a custom ForeignKey class, which is what I will be probably doing, ...
class UserWithProfileField(models.ForeignKey):
def __init__(self, *args, **kwargs):
kwargs.setdefault('to', UserWithProfile)
kwargs.setdefault('on_delete', models.CASCADE)
super().__init__(*args, **kwargs)
... however that couldn't stop future users from using the ForeignKey class with a different on_delete behavior.
Instead of setdefault, you can override the on_delete parameter:
class UserWithProfileField(models.ForeignKey):
def __init__(self, *args, **kwargs):
kwargs['to'] = UserWithProfile
kwargs['on_delete'] = models.CASCADE
super().__init__(*args, **kwargs)
regardless what the user will now use for to=… or on_delete=…, it will use UserWithProfile and CASCADE.
Strictly speaking one can of course still try to alter the attributes of the ForeignKey, but that is more complicated, especially since Django constructs a ForeignObjectRel object to store relation details.
Note that a proxy model [Django-doc] is not used to add exta fields to the model. THis is more to alter the order, etc. and define new/other methods.
I don't get the invariants you are starting out with:
It's irrelevant whether you want to delete references to User or UserWithProfile since these are the same table?
You cannot police what other tables and model authors do and in which way shape or form they point to this table. If they use any kind of ForeignKey that's fine, but they could also point to the table using an unconstrained (integer?) field.
Could you make a test that bootstraps the database and everything, iterates over all models (both in this app and others) and checks every ForeignKey that is there to see if it is pointing to this model and it is setup correctly? That should serve the intended goal I believe.

How to get related objects from one to many relationship for display in ListView and be able to filter?

I'm looking at this tutorial from the Mozilla library. I want to create a list view in admin based on a database relationship. For example I have a Vehicle model and a statusUpdate model. Vehicle is a single instance with many statusUpdates. What I want to do is select the most recent statusUpdate (based on the dateTime field I have created) and have that data available to me in the list view.
The tutorial mentions:
class Vehicle(models.Model):
class statusUpdate(models.Model):
vehicle = models.ForeignKey(Vehicle, on_delete=models.CASCADE)
Question: How could I do a list view with model relationships and be able to filter by fields on the child relationship and pass to the view?
Here's what I wanted in a Class Based View (CBV), my explanation of my issue was not very clear.
def get_context_data(self, **kwargs):
get_context_data is a way to get data that is not normally apart of a generic view. Vehicle is already provided to the View because its the model defined for it, if you wanted to pass objects from a different model you would need to provide a new context, get_context_data is the way to do this. statusUpdate is a model with a foreign key to Vehicle. Full example below.
class VehicleDetail(generic.DetailView):
model = Vehicle
template_name = 'fleetdb/detail.html'
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super(VehicleDetail, self).get_context_data(**kwargs)
context['updates'] = statusUpdate.objects.filter(vehicle_id=1).order_by('-dateTime')[:5]
return context
I don't think that solves your problem entirely. You used this:
context['updates'] = statusUpdate.objects.filter(vehicle_id=1).order_by('-dateTime')[:5]
This will only result in a list of statusUpdates where vehicle_id is set to 1. The part I was struggling with is how to get the primary key (in your case the actual vehicle_id). I found this solution:
vehicle_id = context['vehicle'].pk # <- this is the important part
context['updates'] = statusUpdate.objects.filter(vehicle_id=vehicle_id).order_by('-dateTime')[:5]
I discovered the context object and it contains the data which has already been added (thus you need to call super before using it). Now that I write it down it seems so obvious, but it took me hours to realize.
Btw. I am pretty new to Django and Python, so this might be obvious to others but it wasn't to me.

Setting fields in Django form, when using ModelForm

My model is that I have a list of jobs, and each job can only be done by a subset of users (ManyToManyField). People can then submit job requests, and assign the job to someone from the subset of people who can do the job:
class Job(models.Model):
...
users = models.ManyToManyField(User)
class Job_Request(models.Model):
...
job = models.ForeignKey(Job)
assigned_to = models.ForeignKey(User)
I then created a form using ModelForm, to allow people to edit the job request, to reassign the job to someone else. My problem, is that ModelForm creates a menu for the "assigned_to" field in the form, which lists all of our users. I only want it to show the subset of users that can do that job. How can I do this?
Below is my forms.py, where I tried setting the assigned_to field to the subset of users that can do the job, but I don't know the correct syntax. The following is definitely wrong, as it creates an empty menu. How can I do this, either in the form, or the template? Thanks.
class EditJobRequestForm(ModelForm):
def __init__(self, *args, **kwargs):
super(EditJobRequestForm, self).__init__(*args, **kwargs)
self.fields['assigned_to'].queryset =
User.objects.filter(username__in=self.instance.job.users.all()]
self.fields['assigned_to'].queryset = self.instance.job.users.all()
Remember that job must be set in instance before send model to form
User.objects.filter(username__in=self.instance.job.users.all()]
You're querying against usernames vs PKs. Of course there is no match.
User.objects.filter(pk__in=self.instance.job.users.all()]

Django ModelChoiceField using distinct values from one model attribute

so I'm working on a django application where I have a model Event. Each Event has some attributes, say one of them is "hostname" (which I will use throughout as an example). I need to implement search functionality where a user can search for all events that has hostname == some_value, e.g. hostname == "myhost.foo.bar".
Now, I wanted to allow the user to select among the valid options (i.e. the hostnames that actually exist in one or more event) in a combobox in the search form, so I use ModelChoiceFields for my form. My subclass of ModelChoiceView, for displaying the correct label:
class HostnameModelChoiceField(forms.ModelChoiceField):
def label_from_instance(self, obj):
return obj.host.hostname
My form:
class QueryForm(forms.Form):
hostname = HostnameModelChoiceField(queryset=Event.objects.all(), required=False)
...
However, this gives duplicates because many events may have the same hostname. I attempted using "distinct()" on the queryset but of course that won't work because the objects are distinct (even if the displayed values are not).
So, I tried to instead select only the values I need:
class QueryForm(forms.Form):
hostname = ModelChoiceField(queryset=Event.objects.all().values_list("hostname", "hostname").distinct(), required=False)
But this won't validate! I suspect because the values are not actual Event instances but just string values.
So I tried a regular ChoiceField:
hostname = forms.ChoiceField(choices=Event.objects.all().values_list("hostname", "hostname").distinct(), required=False)
This works, BUT this list is only populated once, so it's not up to date with the database.
So... Is there any good way of solving this problem? To recap the question: HOW do I populate a combo box with the distinct values from one of the fields of a model, and also keeping it in-sync with the database? I would think that a ModelChoiceField would be the best bet, if I can get it to validate when using .values(...) or .values_list(...).
Sincerely,
Hallgeir
The second way will work, but you need to set the choices on init so its refreshed each time the form is called.
e.g
class QueryForm(forms.Form):
hostname = forms.ChoiceField(choices=[], required=False)
def __init__(self, *args, **kwargs):
super(QueryForm, self).__init__(*args, **kwargs)
self.fields['hostname'].choices = Event.objects.all().values_list("hostname","hostname").distinct()

Optimizing ModelChoiceField query in django Admin (AppEngine)

I have two models: Activity and Place.
The Activity model has a ReferenceProperty to the Place model.
This was working fine until the Place table started growing and now
when trying to edit an Activity via django admin I get a memory error
from Google (it doesn't happen if I remove that field from the Activity
admin's fieldsets)
The widget used to edit the RefrenceProperty uses Place.all() to get
the possible values.
As both Activity and Place are sharded by a city property I would like
to optimize the widget's choice query from Place.all() to just the
relevant places, for example Place.all().filter("city =", )
I couldn't find a way to override the query in the docs and I was
wondering if the above is even possible? and if so, how?
Managed to optimize the query by overriding the admin form:
class ActivityAdminForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(ActivityAdminForm, self).__init__(*args, **kwargs)
self.fields['place'].queryset = <... my query ...>
class ActivityAdmin(admin.ModelAdmin):
form = ActivityAdminForm