Django manytomany accessing related column - django

I'm trying to get my head around how Django understands m2m relationships, in SQL you would just add some joins through the intermediate table.
I have a Container which contains various Samples. A Sample can be spread over various Containers.
So in my container I add a alias samples m2m field (essentially a book mark to the other table).
What I can do is get a single Container and display the form information, I would like to add the Sample columns to the form, if I do this for the samples m2m field it returns a multifield, but how do I access the other related fields through the m2m sample_id >=< container_id ?
class Container(models.Model):
container_id = models.AutoField(primary_key=True)
samples = models.ManyToManyField(Sample, through='JoinSampleContainer', through_fields=('container_id', 'sample_id'), related_name='container')
location_id = models.ForeignKey(Location, db_column='location_id', on_delete = models.PROTECT)
icon_desc = models.ForeignKey(Icon, db_column='icon_desc', null=True, blank=True, default='Box',on_delete = models.PROTECT)
container_name = models.CharField(max_length=50, blank=True, null=True)
container_type = models.CharField(max_length=50, blank=True, null=True)
In my Sample table I add the containers alias to act as a bookmark to the other table
class Sample(models.Model):
sample_id = models.AutoField(primary_key=True)
containers = models.ManyToManyField(Container, through='JoinSampleContainer', through_fields=('sample_id', 'container_id'), related_name='sample')
sample_number = models.IntegerField()
material_type = models.CharField(max_length=200, default='', blank=True, null=True, choices = MATERIALS)
weight = models.DecimalField(max_digits=6, decimal_places=2)
description = models.CharField(max_length=500, default='', blank=True, null=True)
recovery_method = models.CharField(max_length=200, default='', blank=True, null=True, choices = RECOVERY_METHODS)
comments = models.CharField(max_length=1000, default='', blank=True, null=True)
In this case I am managing the through table:
class JoinSampleContainer(models.Model):
id = models.AutoField(primary_key=True)
container_id = models.ForeignKey(Container, db_column='container_id', on_delete = models.PROTECT)
sample_id = models.ForeignKey(Sample, db_column='sample_id', on_delete = models.PROTECT)
So now I want to display the contents of a single container through a form. I have the url's setup to pass the container_id.
# views.py
def containercontents(request, pk):
post = get_object_or_404(Container, pk=pk)
# objects = Container.samples.all()
if request.method == "POST":
form = ContainerContentsForm(request.POST, instance=post)
if form.is_valid():
post = form.save(commit=False)
#post.user = request.user
#post.datetime = datetime.datetime.now()
post.save()
return redirect('allcontainer')
#, pk=post.pk)
else:
form = ContainerContentsForm(instance=post)
return render(request, 'container/containercontents.html', {'form': form})
The Form
# form.py
class ContainerContentsForm(forms.ModelForm):
class Meta:
model = Container
fields = (
'location_id',
'container_name',
'container_type',
'icon_desc',
'samples',
)
N.B. The samples seems to list everything regardless of the container.
Then the html
# html
contents
which passes to:
# html
<div class="">
{{ form }}
</div>

Your models are defined wrongly: You should not define the ManyToManyField on both models, only on one of them. So remove the containers field on Sample and only keep it on Container. Set the related_name to "containers" (plural). That way the relationship Container -> Sample is container.samples.all() and the reverse one is sample.containers.all().
Now the purpose of a form is to allow you to select which Samples you want to associate to a Container. So by default the field will be represented by a ModelMultipleChoiceField. The already associated Samples should be pre-selected when you initialise the form with a Container instance.
You can narrow the samples to select from by specifying the queryset for the field, by overriding the default field in the form:
class ContainerContentsForm(forms.ModelForm):
class Meta:
# same code here
samples = forms.ModelMultipleChoiceField(
queryset = Sample.objects.filter(...)
)
You say you want to "display the contents of a Container through the form", if you just want to display, why use a form? To just display the contents, loop through the related samples and display them:
{% for sample in form.instance.samples.all %}
{{ sample.sample_id }}
{% endfor %}
Note: You should rename your ids to id. sample.sample_id is bad programming style. But I already told you that.

Related

Trying to get ForeignKey's names instead of pk in a QuerySet Django

Im trying to make a complex queryset and I want to include my ForeignKeys names instead of pk. I'm using ajax to get a live feed from user inputs and print the results on a DataTable but I want to print the names instead of the pk. Im getting a queryset and when I console.log it, sensor_name is not in there.
My models are like this:
class TreeSensor(models.Model):
class Meta:
verbose_name_plural = "Tree Sensors"
field = models.ForeignKey(Field, on_delete=models.CASCADE)
sensor_name = models.CharField(max_length=200, blank=True)
datetime = models.DateTimeField(blank=True, null=True, default=now)
longitude = models.DecimalField(max_digits=22, decimal_places=16, blank=True, null=True)
latitude = models.DecimalField(max_digits=22, decimal_places=16, blank=True, null=True)
class TreeSensorMeasurement(models.Model):
class Meta:
verbose_name_plural = "Tree Sensor Measurements"
sensor = models.ForeignKey(TreeSensor, on_delete=models.CASCADE)
datetime = models.DateTimeField(blank=True, null=True, default=None)
soil_moisture_depth_1 = models.DecimalField(max_digits=15, decimal_places=2)
soil_moisture_depth_2 = models.DecimalField(max_digits=15, decimal_places=2)
soil_moisture_depth_1_filtered = models.DecimalField(max_digits=15, decimal_places=2, blank=True, null=True)
soil_moisture_depth_2_filtered = models.DecimalField(max_digits=15, decimal_places=2, blank=True, null=True)
soil_temperature = models.DecimalField(max_digits=15, decimal_places=2)
And my view looks like this(I've omitted the non-essential code):
field_list = Field.objects.filter(user=request.user)
tree_sensors = TreeSensor.objects.filter(field_id__in=field_list.values_list('id', flat=True))
statSensors = (TreeSensorMeasurement.objects
.filter(sensor_id__in=tree_sensors.values_list('id', flat=True))
.filter(datetime__date__lte=To_T[0]).filter(datetime__date__gte=From_T[0])
.filter(soil_moisture_depth_1__lte=To_T[1]).filter(soil_moisture_depth_1__gte=From_T[1])
.filter(soil_moisture_depth_2__lte=To_T[2]).filter(soil_moisture_depth_2__gte=From_T[2])
.filter(soil_temperature__lte=To_T[3]).filter(soil_temperature__gte=From_T[3])
.order_by('sensor', 'datetime'))
TreeData = serializers.serialize('json', statSensors)
The code above works correctly but I cant figure out the twist I need to do to get the TreeSensors name instead of pk in the frontend. An example of how I receive one instance in the frontend:
datetime: "2022-11-20T13:28:45.901Z"
​​​sensor: 2
​​​soil_moisture_depth_1: "166.00"
​​​soil_moisture_depth_1_filtered: "31.00"
​​​soil_moisture_depth_2: "171.00"
​​​soil_moisture_depth_2_filtered: "197.00"
soil_temperature: "11.00"
Since sensor_name is at the other end of a foreign key field in your TreeSensorMeasurement object, you can grab it using select_related, as in:
.filter(soil_temperature__lte=To_T[3]).filter(soil_temperature__gte=From_T[3])
.select_related('sensor')
.order_by('sensor', 'datetime'))
In a template you could then refer to it, without further DB lookups, as:
{% for ss in statsensors }}
{{ ss.sensor.sensor_name }}
{% endfor }}
If you're using Django Rest Framework you'll need to do a little more - this is adapted from the example at DRF nested relationshipos
class TreeSensorSerializer(serializers.ModelSerializer):
class Meta:
model = TreeSensor
fields = ['sensor_name']
class TreeSensorMeasurementSerializer(serializers.ModelSerializer):
sensor = TreeSensorSerializer(read_only=True)
class Meta:
model = TreeSensorMeasurement
fields = ['sensor',
'datetime',
'soil_moisture_depth1',
'soil_moisture_depth2',
'soil_temoperature',
]
Then when you call serialise it, you'll need to call your serialiser explicitly, so instead of
TreeData = serializers.serialize('json', statSensors)
use
TreeData = TreeSensorMeasurementSerializer(statSensors, many=True).data
The use of .data at end allows the prefetched data relationship to be used without further lookups
Try adding a read-only serializer field in TreeSensorMeasurementSerializer
sensor_name = serializer.ReadOnlyField(source='sensor.sensor_name')

accessing product sub categories like ebay in django form

i am trying to replicate the category structure sites like Ebay use when posting a product. For example, if you want to post an Iphone for sale, you go to post an ad, choose a category from the drop down menu on the form('electronics & computers'), then choose a subcategory of "phones", then the last sub category of "iphone". To create this structure i am using django-categories. Creating a product in the admin page works file, the admin form allows me to choose from a drop down menu of every category, however i can't seem to replicate that same process on my own form, to give users the ability to choose from the many categories.
If your not aware if django-categories it is a Modified Preorder Tree Traversal.
Here is my advert model
class Advert(models.Model):
title = models.CharField(max_length=100, blank=False, null=False)
author = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, on_delete=models.SET_NULL)
image = models.ImageField(upload_to='media/', blank=True, null=True)
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True)
quantity = models.IntegerField(blank=False, null=False)
condition = models.CharField(max_length=10, choices=COND_CATEGORIES, blank=False, null=False)
price = models.DecimalField(decimal_places=2, max_digits=14, blank=False, null=False)
description = models.TextField(blank=False, null=False)
date_posted = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
active = models.BooleanField(default=True)
featured = models.BooleanField(default=False)
search_vector = SearchVectorField(null=True, blank=True)
Here is the category model
class Category(CategoryBase):
class Meta:
verbose_name_plural = 'categories'
And here i the form that allows users to post an advert
class PostAdvertForm(forms.ModelForm):
title = forms.CharField(label='Ad Title', required=True)
category = forms.ChoiceField(choices=Advert.category, label='Choose a category', required=True)
price = forms.DecimalField(label='Price', required=True, widget=forms.TextInput())
description = forms.CharField(widget=forms.Textarea(attrs={'placeholder':
('Please provide a detailed description'),
'autofocus': 'autofocus'}), label='Description', required=True)
condition = forms.ChoiceField(choices=Advert.COND_CATEGORIES, label='Condition', required=True)
quantity = forms.IntegerField(label='Quantity', required=True, widget=forms.TextInput())
image = forms.ImageField(label='Upload an image', required=False)
class Meta:
model = Advert
fields = (
'title', 'category', 'quantity', 'condition', 'price', 'description',
'image')
Using advert.category on the choice field is not working due to "ForwardManyToOneDescriptor' object is not iterable".
My question is how can i get the category list to appear on the choicefield?
EDIT:
Was just wondering, would this be possible to implement entirely with jquery on the front end and just send through the choosen category through the cleaned data? This way i would not even need to bother using django-categories on the backend, i could just store a massive load of categories.
2nd EDIT:
Looks like i got the first part of this to work, i have pasted the new form code below:
at = Advert.category.get_queryset()
class PostAdvertForm(forms.ModelForm):
title = forms.CharField(label='Ad Title', required=True)
category = forms.ModelChoiceField(queryset=cat, label='Choose a category', required=True)
price = forms.DecimalField(label='Price', required=True, widget=forms.TextInput())
description = forms.CharField(widget=forms.Textarea(attrs={'placeholder':
('Please provide a detailed description'),
'autofocus': 'autofocus'}), label='Description', required=True)
condition = forms.ChoiceField(choices=Advert.COND_CATEGORIES, label='Condition', required=True)
quantity = forms.IntegerField(label='Quantity', required=True, widget=forms.TextInput())
image = forms.ImageField(label='Upload an image', required=False)
class Meta:
model = Advert
fields = (
'title', 'category', 'quantity', 'condition', 'price', 'description',
'image')
I have never used jquery before, is that the best option to create a flowing drop down style menu? I must make it so they can't choose a parent category, only the bottom child category.
Define a function update_form_field_choices (for example in utils.py):
def update_form_field_choices(field, choices):
"""
Update both field.choices and field.widget.choices to the same value (list of choices).
"""
field.choices = choices
field.widget.choices = choices
Then, define method __init__ in PostAdvertForm:
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
And in this function, call this function, and replace your_choices() with a function that returns the correct choices. Remember the choices format (a tuple of tuples).
update_form_field_choices(field=self.fields['category'], choices=your_choices())
Also remember, ModelForm defines the form fields for you, so you don't have to define them explicitly, unless you want to change something in the default definition.

Add Queryset to django updateview

I have an updateview in which a manager can go and edit all the fields for the associate. Looks like this:(requirement is to add associate_mgr in the as a dropdown in the updateview)enter image description here
views.py
class ReallocationTeam(LoginRequiredMixin,UpdateView):
model = UserDetails
form_class = ViewEditSample
def get_success_url(self):
return reverse('UserProfile:index')
forms.py
class ViewEditSample(ModelForm):
class Meta:
model = UserDetails
fields = ['associate_name','client','lob','associate_mgr']
The manager should be able to edit the "assciate_mgr" of that associate too.
models.py
associate_name = models.CharField(max_length=200)
associate_nbr = models.CharField(max_length=8, primary_key=True)
associate_email = models.EmailField()
associate_department_id = models.CharField(max_length=50)
associate_mgr = models.CharField(max_length=100,blank=True, null=True)
associate_exec = models.CharField(max_length=100,blank=True, null=True)
associate_org = models.CharField(max_length=100,blank=True,null=True)
title = models.CharField(null=True, blank=True, max_length=100)
date_of_service = models.CharField(null=True,blank=True,max_length=11)
is_manager = models.BooleanField(default=False)
is_exec = models.BooleanField(default=False)
is_team_lead = models.BooleanField(default=False)
but associate_mgr is not a choice field in my db.
I need to add a dropdown that contains associate_mgr in my UpdateView. How do I go about implementing that?
Should I go about writing a query to get all managers and populate them i a dropdow: like this mgr = UserDetails.objects.filter(is_manager=True) But then how do i store the selected in associate_mgr field in db?
You can override your form field in your ModelForm to be a ChoiceField with a list of choices: UserDetails.objects.filter(is_manager=True).values_list('name').
associate_mgr = forms.ChoiceField(choices=
UserDetails.objects.filter(is_manager=True).values_list('associate_name', 'associate_name')
)
Then the choice will automatically be saved (the 'associate_name' field value).
But it would probably be a better idea to use a ForeignKey on your model, rather than a CharField. That would enforce the values to be other UserDetails rather than just a string.

Django: Problem with filtering products in order

I'm trying to make account view in my django-shop. I want to display information about the order and the ordered goods. I have a ProductInOrder model with foreign key to Order. Now I want to filter the ordered goods by order. But something is going wrong.
class Order(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
ref_code = models.CharField(max_length=15)
items = models.ForeignKey(Cart, null=True ,on_delete=models.CASCADE, verbose_name='Cart')
total = models.DecimalField(max_digits=9, decimal_places=2, default=0.00)
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100, blank=True)
phone = models.CharField(max_length=20)
buying_type = models.CharField(max_length=40, choices=BUYING_TYPE_CHOICES,
default='Доставка')
address = models.CharField(max_length=250, blank=True)
date_created = models.DateTimeField(default=timezone.now)
date_delivery = models.DateTimeField(default=one_day_hence)
comments = models.TextField(blank=True)
status = models.CharField(max_length=100, choices=ORDER_STATUS_CHOICES,
default='Принят в обработку')
class ProductInOrder(models.Model):
order = models.ForeignKey(Order, on_delete=models.CASCADE)
product = models.ForeignKey(Product, on_delete=models.CASCADE)
quantity = models.IntegerField(default=1)
item_cost = models.DecimalField(max_digits=10, decimal_places=2, default=0.00)
all_items_cost = models.DecimalField(max_digits=10, decimal_places=2, default=0.00)
And views.py
def account_view(request):
order = Order.objects.filter(user=request.user).order_by('-id')
products_in_order = ProductInOrder.objects.filter(order__in=order)
categories = Category.objects.all()
instance = get_object_or_404(Profile, user=request.user)
if request.method == 'POST':
image_profile = ProfileImage(request.POST, request.FILES, instance=instance)
if image_profile.is_valid():
avatar = image_profile.save(commit=False)
avatar.user = request.user
avatar.save()
messages.success(request,
f'Ваш аватар был успешно обновлен!')
return redirect('ecomapp:account')
else:
image_profile = ProfileImage()
context = {
'image_profile': image_profile,
'order': order,
'products_in_order': products_in_order,
'categories': categories,
'instance': instance,
}
return render(request, 'ecomapp/account.html', context)
This line products_in_order = ProductInOrder.objects.filter(order__in=order) doesn't work.
Any help please.
Unless you explicityly mention order_by in ProductInOrder queryset, it will order by its default setup, which is mentioned in ProductInOrder model's meta class(if its not mentioned, then default ordering is pk). So using following line should resolve your issue:
ProductInOrder.objects.filter(order__in=order).order_by('-order')
But an improved answer is like this:
products_in_order = ProductInOrder.objects.filter(order__user=request.user).order_by('-order')
In this way, you can remove line order = Order.objects.filter(user=request.user).order_by('-id') from your code. Whats happening here is that, django allows nested filtering, so you can filter by order__user which will allow you order by user from Order model. You don't need to make a filter for Order separately.
Update:
I am not sure, probably you are looking for this:(in template)
{% for o in order %}
{% for po in o.productinorder_set.all %}
{{ po.product }}
{{ po.item_cost }}
{% endfor %}
{% endfor %}
Here I am using reverse relation between Order and ProductInOrder here.

django retrieve fields from few tables in template

class PlannedOTList(models.Model):
patient = models.ForeignKey(Patient, on_delete=models.CASCADE)
date_added = models.DateTimeField(auto_now_add=True)
planned_surgery = models.TextField(verbose_name='diagnosis and planned surgery', blank=True) # decided by the committee
planned_date_of_surgery = models.DateField('date of surgery', null=True, blank=True)
planned_date_of_admission = models.DateField('date of admission', null=True, blank=True)
remarks = models.TextField(blank=True)
surgery_set = models.BooleanField('required surgery set', default=False)
# to_be_admitted = models.BooleanField(default=False)
hide = models.BooleanField(default=False)
objects = PlannedOTListQS.as_manager()
class Meta:
db_table = 'planned_ot_list'
ordering = ['-date_added']
class Admission(models.Model):
# general info
date_admission = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
patient = models.ForeignKey(Patient, on_delete=models.CASCADE)
class OperationNotesList(models.Model):
admission=models.ForeignKey(Admission,on_delete=models.CASCADE,null=True)
#patient=models.ForeignKey(Patient,on_delete=models.CASCADE)
date_added=models.DateTimeField(auto_now_add=True)
procedure_code=models.CharField(max_length=7)
diagnosis_code=models.CharField(max_length=10)
created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, null=True, related_name='op_created_by')
pre_operation_list=models.CharField(max_length=70,blank=True)
intra_operation_list=models.CharField(max_length=70,blank=True)
post_operation_list=models.CharField(max_length=70,blank=True)
is_done=models.BooleanField(default=False)
class Meta:
db_table='operationNotesList'
class Patient(models.Model):
patientid_generated_part = models.CharField(max_length=5, default='', blank=True)
date_recorded = models.DateTimeField(default=timezone.now)
modified = models.DateTimeField(auto_now=True, null=True)
first_name = models.CharField(max_length=50)
class Meta:
db_table = 'patients'
ordering = ['-modified']
HTML Code:
<div class="row">
<div class="col-xs-6 col-md-3"><label >Proposed Operation: &nbsp
{{ operationnoteslist.admission.patient.planned_ot_list.planned_surgery }}</label></div>
<div class="col-xs-6 col-md-3"><label >Weight: &nbsp
{{ operationnoteslist.admission.weight }}&nbsp(kg)</label></div>
<div class="col-xs-6 col-md-3"><label >Height: &nbsp
{{ operationnoteslist.admission.height }}&nbsp(cm)</label></div>
<div class="col-xs-6 col-md-3"><label >BMI: &nbsp
{{ operationnoteslist.admission.bmi }}</label></div>
</div>
the html code above has the main model operationnoteslist.
I am trying to get values from planned_ot_list. I don't know what I am missing.
I thought the way to go is: MyownModelTable.foreignTablename.foreignTablename.field
The Proposed operation does not retrieve any values.
(As reply to the comment: There is no need for a planned_ot_list in the Patient model.)
Reverse relations (one to many) have by default a _set suffix. Also, in your PlannedOTList model, patient has not the unique flag so a patient can have several of those related to them. And on top, the model relation name in lowercase has no underscores (camel case is simply lower cased). So the reverse relation name should be:
patient.plannedotlist_set
(You can print out the available properties using dir(patient), the output will include the reverse relation properties.)
This returns a query manager and you cannot simply write patient.plannedotlist_set.planned_surgery. Instead, you have to decide whether to display the complete list or only one of its entries. If they have a natural order and you want to use the first or last, you can do this:
patient.plannedotlist_set.first # in the template or first() in view
patient.plannedotlist_set.last
To iterate over all of them use:
patient.plannedotlist_set.all # template or all() in view
Note that you should give the PlannedOTList an ordering to make this work, either by adding a Meta property like this:
Meta:
ordering = ('field1', 'field2', ...) # use '-field1' for reverse
Or, if the ordering is dependent of the view, order in the view and add the list to the template context explicitly.