form table creation, template display and view processing - django

[This is a refactor of a previous question, and I hope this question is more clear.]
I have a an activity model that holds a number and is dependent on a person and a category.
class Activity( models.Model ):
person = models.ForeignKey( Person ) # approx 80 people
category = models.ForeignKey( Category ) # approx 6 categories
value = models.FloatField( )
The users would like to have a table with category across the top and person down the side and then a value field in each cell if the table.
Currently my form is:
class MonthlyActivityForm(forms.Form):
def __init__(self, *args, **kwargs ):
affiliations = kwargs.pop('affiliations')
point_categories = kwargs.pop('point_categories')
super(MonthlyActivityForm, self).__init__(*args, **kwargs)
for pc in point_categories:
for a in affiliations:
self.fields['pc-%d-pe-%d' % (pc.id, a.person.id)] = forms.FloatField()
I can't see how to do a table of form fields (nicely) in regular Django. I think it comes down to the form building which will affect the efficiency of the template and view processing.
First pass thought is to:
1) create a FloatField for each person,category create a template to create a table as described above (this part seems harder than it should be)
2) in the view, on a valid form run through each FloatField in the returned form and save any changes
So, I suppose there are two questions:
1) is there a more efficient method to create a table of FloatFields other than trying to figure out looping in the template to create a table with FloatFields in the td's?
2) how can I store the person.id and category.id in the FloatField such that when I go back to the view with a valid form then I can create/update the proper Activity entry? (or can I just grab it from the form key for each FloatField?)
I am reasonably new to Django, but it seems like it should be slightly easier. I have to be missing something here.

You could make this much simpler by using formsets. In this case I would probably have one form per user, containing all the category fields created dynamically, but the formset would take care of instantiating one of these forms per user.

Related

Django ManyToManyField with through -- how can I not lose data not selected in the QuerySet?

Django Newbie here. I have some trouble with a ManyToMany relationship and a through field.
I am trying to create a tool where staff members can apply for shifts on events (Bar, entry control, etc.). Therefore I created a Staff object (this is the person with all her roles etc.) and linked it by ManyToManyField to a ScheduledShift object (which contains event date, time and duties).
I want to be able to present each day as a view to the user where he/she can just tick the shifts he/she is available that day. This works and it also writes the correct data (i.e. records with the staff id and the shift id for all the shifts he/she ticked) for that day into the "through" table of the StaffShift class.
The relevant code looks like this:
models.py
class Staff(models.Model):
...
shifts = models.ManyToManyField(ScheduledEventShift, through='StaffShift')
class StaffShift(models.Model):
staff = models.ForeignKey(Staff, on_delete=models.CASCADE)
shift = models.ForeignKey(ScheduledEventShift, on_delete=models.CASCADE)
views.py
class StaffShiftUpdateView(ObjectUpdateView):
model = Staff
...
def get_form(self, form_class=None):
form = super().get_form(form_class=self.form_class)
day = datetime(self.kwargs.get('year'), self.kwargs.get('month'), self.kwargs.get('day'))
form.fields['shifts'].queryset = ScheduledEventShift.objects.filter(
event__event_date=day).order_by('event__event_date')
return form
('event' is a property in the ScheduledEventShift object)
My problem is that when saving the form all other already existing rows for that user that were not selected for the form's queryset (i.e. all other shifts that he/she has already ticked for other days) are removed in the through db table. This might be intended orm behaviour, but in my case the desired behaviour would be that records stay in the through table unless "their" day is shown in the form and they are unticked there.
What is the best way to keep those records in the through db table that were not selected in the queryset for the form?
One thing that I figured out that seems to work is getting all StaffShifts for that user that are not on that day before the form.save() and recreating them after the form.save():
def form_valid(self, form):
other = StaffShift.objects.filter(staff__user__id=self.request.user.id).exclude(shift__event__event_date=day)
form.save(commit=True)
for o in other:
StaffShift.objects.update_or_create(admin_confirmed=o.admin_confirmed, staff_shift_info=o.staff_shift_info,
shift_id=o.shift_id, staff_id=o.staff_id)
...
As this feels more like a hack than a proper way to do things... Is there any better way to do that? It also doesn't seem to be efficient once there are many many shifts in the db ;-)

Flask-admin editable select column row-based filter

I am using flask-admin to have easy edits on my DB model. It is about renting out ski to customers.
This is my rental view (the sql_alchemy DB model is accordingly):
class RentalView(ModelView):
column_list = ("customer", "from_date", "to_date", "ski", "remarks")
...
customer and ski are relationship fields to the respective model. I want to only show these ski in the edit view that are not rented by others in this time period.
I have been searching everywhere how to dynamically set the choices of the edit form but it simply does not work fully.
I tried doing
def on_form_prefill(self, form, id):
query = db.session.query... # do the query based on the rental "id"
form.ski.query = query
and this correctly shows the filtered queries. However, when submitting the form, the .query attribute of the QuerySelectField ski is None again, hence leading to a query = self.query or self.query_factory() TypeError: 'NoneType' object is not callable error. No idea why the query is being reset?!
Does anybody know a different strategy of how to handle dynamic queries, based on the edited object's id?
Use this pattern, override the view's edit_form method, instance the default edit form (by calling the super() method then modify the form as you wish:
class RentalView(ModelView):
# setup edit forms so that Ski relationship is filtered
def edit_form(self, obj):
# obj is the rental instance being edited
_edit_form = super(RentalView, self).edit_form(obj)
# setup your dynamic query here based on obj.id
# for example
_edit_form.ski.query_factory = lambda: Ski.query.filter(Ski.rental_id == obj.id).all()
return _edit_form

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.

Django models: Reference field return multiple type of models

As a project to figure out Django I'm trying to build a small game.
A player has a base. A base has several type of items it can harbor. (Vehicle, Defense, Building).
I have 3 static tables which contain information for the first level of each item (in the game these values are used in formulas to calculate stuff for upgrades). I've used a sequence to insert all these items in these different tables so the ID's are unique across tables.
To keep track of what items the player has per base I have a table 'Property'. I want to use a single field as a reference to the ID of an item and trying to get this done with the Django models.
Warning: my knowledge about Django models are pretty limited and I've been stuck with this a few days now.
Is this possible and if so how can it be done?
I tried using annotations on the save method to change the value of a field by overwriting the field with the id of that object before trying to query the object by id when trying to 'get' the object, however I can't get past the obvious restriction of the model when defining that field as an Integer - I hoped it wouldn't validate until I called save()
def getPropertyItemID(func):
"""
This method sets the referral ID to an item to the actual ID.
"""
def decoratedFunction(*args):
# Grab a reference to the data object we want to update.
data_object=args[0]
# Set the ID if item is not empty.
if data_object.item is not None:
data_object.item=data_object.item.id
# Execute the function we're decorating
return func(*args)
return decoratedFunction
class Property(models.Model):
"""
This class represents items that a user has per base.
"""
user=models.ForeignKey(User)
base=models.ForeignKey(Base)
item=models.IntegerField()
amount=models.IntegerField(default=0)
level=models.SmallIntegerField(default=0)
class Meta:
db_table='property'
#getPropertyItemID
def save(self):
# Now actually save the object
super(Property, self).save()
I hope you can help me here. The end result I'd like to be able to put to use would be something like:
# Adding - automatically saving the ID of item regardless of the class
# of item
item = Property(user=user, base=base, item=building)
item.save()
# Retrieving - automatically create an instance of an object based on the ID
# of item, regardless of the table this ID is found in.
building = Property.objects.all().distinct(True).get(base=base, item=Building.objects.all().distinct(True).get(name='Tower'))
# At this point building should be an instance of the Building model
If I'm completely off and I can achieve this differently I'm all ears :)
I think you are looking for a Generic Relationship:
class Property(models.Model):
user=models.ForeignKey(User)
base=models.ForeignKey(Base)
content_type = models.ForeignKey(ContentType) # Which model is `item` representing?
object_id = models.PositiveIntegerField() # What is its primary key?
item=generic.GenericForeignKey('content_type', 'object_id') # Easy way to access it.
amount=models.IntegerField(default=0)
level=models.SmallIntegerField(default=0)
This lets you create items as you mentioned, however you would probably need to look at a different way of filtering those items out.

Dynamic model choice field in django formset using multiple select elements

I posted this question on the django-users list, but haven't had a reply there yet.
I have models that look something like this:
class ProductGroup(models.Model):
name = models.CharField(max_length=10, primary_key=True)
def __unicode__(self): return self.name
class ProductRun(models.Model):
date = models.DateField(primary_key=True)
def __unicode__(self): return self.date.isoformat()
class CatalogItem(models.Model):
cid = models.CharField(max_length=25, primary_key=True)
group = models.ForeignKey(ProductGroup)
run = models.ForeignKey(ProductRun)
pnumber = models.IntegerField()
def __unicode__(self): return self.cid
class Meta:
unique_together = ('group', 'run', 'pnumber')
class Transaction(models.Model):
timestamp = models.DateTimeField()
user = models.ForeignKey(User)
item = models.ForeignKey(CatalogItem)
quantity = models.IntegerField()
price = models.FloatField()
Let's say there are about 10 ProductGroups and 10-20 relevant
ProductRuns at any given time. Each group has 20-200 distinct
product numbers (pnumber), so there are at least a few thousand
CatalogItems.
I am working on formsets for the Transaction model. Instead of a
single select menu with the several thousand CatalogItems for the
ForeignKey field, I want to substitute three drop-down menus, for
group, run, and pnumber, which uniquely identify the CatalogItem.
I'd also like to limit the choices in the second two drop-downs to
those runs and pnumbers which are available for the currently
selected product group (I can update them via AJAX if the user
changes the product group, but it's important that the initial page
load as described without relying on AJAX).
What's the best way to do this?
As a point of departure, here's what I've tried/considered so far:
My first approach was to exclude the item foreign key field from the
form, add the substitute dropdowns by overriding the add_fields
method of the formset, and then extract the data and populate the
fields manually on the model instances before saving them. It's
straightforward and pretty simple, but it's not very reusable and I
don't think it is the right way to do this.
My second approach was to create a new field which inherits both
MultiValueField and ModelChoiceField, and a corresponding
MultiWidget subclass. This seems like the right approach. As
Malcolm Tredinnick put it in
a django-users discussion,
"the 'smarts' of a field lie in the Field class."
The problem I'm having is when/where to fetch the lists of choices
from the db. The code I have now does it in the Field's __init__,
but that means I have to know which ProductGroup I'm dealing with
before I can even define the Form class, since I have to instantiate the
Field when I define the form. So I have a factory
function which I call at the last minute from my view--after I know
what CatalogItems I have and which product group they're in--to
create form/formset classes and instantiate them. It works, but I
wonder if there's a better way. After all, the field should be
able to determine the correct choices much later on, once it knows
its current value.
Another problem is that my implementation limits the entire formset
to transactions relating to (CatalogItems from) a single
ProductGroup.
A third possibility I'm entertaining is to put it all in the Widget
class. Once I have the related model instance, or the cid, or
whatever the widget is given, I can get the ProductGroup and
construct the drop-downs. This would solve the issues with my
second approach, but doesn't seem like the right approach.
One way of setting field choices of a form in a formset is in the form's __init__ method by overwriting the self.fields['field_name'].choices, but since a more dynamic approach is desired, here is what works in a view:
from django.forms.models import modelformset_factory
user_choices = [(1, 'something'), (2, 'something_else')] # some basic choices
PurchaserChoiceFormSet = modelformset_factory(PurchaserChoice, form=PurchaserChoiceForm, extra=5, max_num=5)
my_formset = PurchaserChoiceFormSet(self.request.POST or None, queryset=worksheet_choices)
# and now for the magical for loop
for choice_form in my_formset:
choice_form.fields['model'].choices = user_choices
I wasn't able to find the answer for this but tried it out and it works in Django 1.6.5. I figured it out since formsets and for loops seem to go so well together :)
I ended up sticking with the second approach, but I'm convinced now that it was the Short Way That Was Very Long. I had to dig around a bit in the ModelForm and FormField innards, and IMO the complexity outweighs the minimal benefits.
What I wrote in the question about the first approach, "It's straightforward and pretty simple," should have been the tip-off.