Django M2M with addititional data with through - python

My problem is as follows. I am saving data for patients from a form on a webpage. The form is generated from model definitions in models.py. The information that I save is name, surname amongst others. I have a field for diagnosis which is selected using a multichoiceField and I save it using manytomany.
When the data is saved, a separate table is created for the diagnosis assigned to each patient as expected. The table contains a diagnosis and the ID of the patient it applies to. Each diagnosis is saved as a separate record.
In addition to selecting the diagnosis, I also save the date that the diagnosis is made. You will see what I mean in the models.py and form.py code below.
I would like to have the date for which the diagnosis was made also saved in the table but I can't figure out how to do this. I have tried following the docs: https://docs.djangoproject.com/en/dev/topics/db/models/#intermediary-manytomany as well as some other posts on SO, but cannot figure out how to do it. I can't figure out how the views, forms and models need to be set up in order to achieve. Is it possible to do this and if so how? I have tried using an intermediate model with manytomany and 'through', but I do not understand it. Any help with this would be greatly appreciated.
Below is a simplified version of my code:
models.py:
class diagnosisChoices(models.Model): #This represents the list in the drop down menu for the different diagnosis.
diagnosis = models.CharField(max_length = 50)
def __str__(self):
return self.diagnosis
class PatientData(models.Model):
Name = models.CharField(max_length=100)
Surname = models.CharField(max_length=100)
dateOfBirth = models.DateField(default = datetime.datetime.now())
diagnosis = models.ManyToManyField(
'diagnosisChoices',
#on_delete=models.CASCADE,
)
views.py:
def patientDataView(request):
uId = request.user.id
if request.method == "POST":
form = PatientDataForm(request.POST)
if form.is_valid():
model_instance = form.save(commit=False)
model_instance.timestamp = timezone.now()
model_instance.save()
#model_instance.add(uId)
form.save_m2m()
return HttpResponseRedirect('/dataBase')
else:
form = PatientDataForm()
return render(request, "dataBaseTest.html", {'form': form})
date_of_diagnosis = models.DateField(default=datetime.datetime.now())
forms.py
from django.forms import ModelForm
from django import forms
from .models import PatientData
from .models import diagnosisChoices #This is the list of diagnosis in the dropdown
from django.forms import extras
import datetime
from functools import partial
class PatientDataForm(ModelForm):
class Meta:
now = datetime.datetime.now()
thisYear = now.year
DateInput = partial(forms.DateInput, {'class': 'datepicker'})
widgets = {
}
model = PatientData
fields = ['Name',
'Surname',
'dateOfBirth',
'diagnosis',
'date_of_diagnosis',
]
Thanks,
Thomas

The main thing that you are not getting is on the models.py, so I will focus on it.
You need three tables to do what you have described: diagnosisData, PatientData and a 'membership' table which I call diagnosisPatient. Then you build your model like this:
class diagnosisChoices(models.Model):
diagnosis = models.CharField(max_length = 50)
class PatientData(models.Model):
Name = models.CharField(max_length=100)
Surname = models.CharField(max_length=100)
dateOfBirth = models.DateField(default = datetime.datetime.now())
diagnosis = models.ManyToManyField('diagnosisChoices',through='diagnosisPatient')
class diagnosisPatient(models.Model):
patient = models.ForeignKey('PatientData')
diagnosis = models.ForeignKey('diagnosisChoices')
dateOfDiagnosis = models.DateField()
Once you have your model built this way, you should save your PatientData and your diagnosisChoices instances as usual. FOr the many to many relation, you should save it manualy on the diagnosisPatient table using the apropriate foreign keys and date. You can query the many to many relation from the PatientData model as usual with objects.all() function.
The thing here to keep in mind is that ManyToMany relations in django are always creating a new membership table for you behind the scenes. So when you do not need to insert extra information on the relationship the diagnosisPatient table is just made of two foreign keys, and it is hidden. The through argument on this relationship is just bringing this table to light and giving you control back to put whatever new relationship you like.

Related

How can I populate a model with non-form data?

I have Student, Class and StudentClass models. I would like a student to be able to join a new class by inputting the class_code into a form. To join a class the user's student_id and the class_code is saved to the StudentClass model. The student_id isn't a form field so should be obtained by querying the Student model with the logged-in user's username and returning the student_id.
models.py:
class StudentClass(models.Model):
class Meta:
unique_together = (('class_code', 'student_id'),)
class_code = models.ForeignKey(Class, on_delete=models.CASCADE)
student_id = models.ForeignKey(Student, on_delete=models.CASCADE)
forms.py:
class JoinClassForm(forms.ModelForm):
class Meta:
model = StudentClass
fields = ['class_code']
exclude = ('student_id',)
views.py
#login_required
def join_class(request):
if request.method == "POST":
joinclass_form = JoinClassForm(request.POST or None)
if joinclass_form.is_valid():
formclass_code = joinclass_form.data.get('class_code')
if Class.objects.filter(class_code=formclass_code).exists():
joinclass_form.save(commit=False)
joinclass_form.student_id =
Student.objects.filter(username=request.user.username).values_list('student_id', flat=True)
joinclass_form.save()
return redirect('assignments')
else:
messages.success(request, ("This class doesn't exist!"))
else:
joinclass_form = JoinClassForm
return render(request, 'join_class.html', {'joinclass_form': joinclass_form})
I've tried using a hidden_field for student_id, excluding student_id from the form fields and saving as commit=False, and I've gotten the same error: IntegrityError Not NULL constraint failed. Is there an error in my code that I have missed or am I using the wrong method?
Thanks in advance
Edit: Forgive me but copy-paste went kinda awry on the last set of code and I have no idea how to fix it
you dont need to put .username at request.user. You can just say request.user
Thanks everyone for your help but I managed to fix it now
In case anyone needs this in the future, I realised that I made 2 mistakes:
I wasn't saving my form correctly in my views.py, I was trying to save the value student_id to the form rather than an object.
https://docs.djangoproject.com/en/4.1/topics/forms/modelforms/#the-save-method
I named my foreign key in my StudentClass table as student_id which meant that Django was renaming it as student_id_id. To fix this, I just removed _id from student_id in the StudentClass table.

Django modelformset_factory: One more Form shown than there should actually be

I have the following model:
class Portfolio(models.Model):
id = models.AutoField(primary_key=True)
member = models.ForeignKey(Member, on_delete=models.CASCADE)
For that I made the ModelForm:
class PortfolioForm(forms.ModelForm):
class Meta:
model = Portfolio
exclude = ['id']
I need many of those in one template so I am creating them in the Following way in my view
def portfolio_form(request, pk):
...
PortfolioFormSet = modelformset_factory(Portfolio, form=PortfolioForm)
formset = PortfolioFormSet(queryset=Portfolio.objects.filter(pk__in=list_of_ids))
finally in the html I have this:
everything is working fine except that one more is shown in HTML than there actually are. I have verified them in the shell. There are 3 but 4 are shown.
I am displaying them in the table. I am positive that it is not the template.
By default, modelformset_factory uses extra=1. Set it to zero if you don't want any extra forms.
PortfolioFormSet = modelformset_factory(Portfolio, form=PortfolioForm, extra=0)

Query Django model using name from another model

I'm building a project that involves a list of people and the transactions they've made. Each person will have their own profile with x amount of transactions.
My database, so far, consists of two models:
One to define a person
class Person (models.Model):
"""
A person
"""
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=100, blank=True)
def __unicode__(self):
return self.name
One to associate a transaction each person made.
class Purchase (models.Model):
"""
A purchase for each person
"""
person_id = models.ForeignKey(Person)
purchase_description = models.CharField(max_length=1000, blank=True)
I determined this would be a many-to-one database relationship because some people might make the same transaction.
Now, I can make pages for each person, showing their name in the header. What I'd like to do is show the transactions made to that particular person. How can this be done? Here's what my view.py file looks like:
from django.shortcuts import render
from .models import Person, Purchase
def load_person(request, name):
person = Person.objects.get(name=name)
# name = Purchase.objects.filter(purchase)
context = {
'name': name
# 'purchase': purchase
}
return render(request, 'pages/person.html', context)
Here's the url associated to the query in my url.py file.
url(r'^project/(?P<name>[-\w]+)/$', views.load_person),
person = Person.objects.get(name=name)
purchases = Purchase.objects.filter(person_id=person)
Sounds like you just started using django, there are several things that are not best practice, they are:
You don't need to define id in Person, django will define it for you.
Try not to use person_id as field name, use person instead. Django model is not relational database design, think of the models as separate entities.

How to save the created object with a many-to-many relationship and map table?

Using the accepted answer in Class-based views for M2M relationship with intermediate model, I am able to save the Membership if I hard code an ID for its foreignkey to Group, despite not having any information for date_joined and invite_reason, but the Group for which the form is filled out never gets saved, and therefore I cannot supply Membership with the correct ID. In fact, any time I try to save thegroup, before or after the code in the accepted answer, I get the same AttributeError as the OP:
Cannot set values on a ManyToManyField which specifies an intermediary model. Use Membership's Manager instead.
Is there a way to make this code actually work? Am I being led astray? What would be the proper way to save something with a many-to-many field that goes through a mapping table? The answers I've found have confused me more than helped me.
For reference, should the question ever be deleted:
models.py
class Person(models.Model):
name = models.CharField(max_length=128)
def __unicode__(self):
return self.name
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Membership')
def __unicode__(self):
return self.name
class Membership(models.Model):
person = models.ForeignKey(Person)
group = models.ForeignKey(Group)
date_joined = models.DateField()
invite_reason = models.CharField(max_length=64)
Accepted Answer (With an additional line to hard code an ID)
from django.views.generic.edit import ModelFormMixin
from django.views.generic import CreateView
class GroupCreate(CreateView):
model = Group
def form_valid(self, form):
self.object = form.save(commit=False)
for person in form.cleaned_data['members']:
membership = Membership()
membership.group = self.object
membership.group_id = 108 # Added line
membership.person = person
membership.save()
return super(ModelFormMixin, self).form_valid(form)
Note: I had to add the id line because I would get the following IntegrityError without it:
Column 'group_id' cannot be null
In case it's pertinent, I am trying to save this to a MySQL database.

Django: combine two ForeignKeys into one field

I need to implement the following:
The user shall be presented with a form that will have a drop down choice menu consisting of property names. There are two types of properties: general properties, i.e. properties common for all users and custom properties, i.e. properties that each user has defined prior to that. The models would look something like that:
class GeneralPropertyName(models.Model):
name = models.CharField(max_length=20)
class CustomPropertyName(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=20)
The drop down menu should have all general properties and only those custom properties that pertain to the user.
First question: how to define such a model?
I need to: 1. somehow unify both properties, 2. take only those items from CustomPropertyName that pertain to the user
class SpecData(models.Model):
user = models.ForeignKey(User)
selection_title = models.CharField(max_length=20)
property = ForeignKey(GeneralPropertyName) ??UNIFY??? ForeignKey(CustomPropertyName)
Second, is there anything special that needs to be done with ModelForm?
class SpecDataForm(ModelForm):
class Meta:
model = SpecData
And the 3rd question is what needs to be done in the view? I will need to use inline formsets since I will have a few dynamic forms like that.
def index(request):
user = User.objects.get(username=request.user.username)
specdataFormSet = inlineformset_factory(User, SpecData, form=SpecDataForm, extra=30)
...
specdata_formset = specdataFormSet(instance=user, prefix='specdata_set')
...
Thanks.
EDIT: Adjusted juliocesar's suggestion to include formsets. Somehow I am getting the following error message: Cannot resolve keyword 'property' into field. Choices are: id, name, selection_title, user
def index(request):
user = User.objects.get(username=request.user.username)
user_specdata_form = UserSpecDataForm(user=user)
SpecdataFormSet = inlineformset_factory(User, SpecData, form=user_specdata_form, extra=30)
You can use a GenericForeignKey to handle it, but you still need more to solve your further questions about forms and view.
I have made an example of how you solve your problem (logged user can select from General properties and his Custom properties, non-logged user only can select General properties). I used model inheritance for the properties (In your sample code it seems that a CustomPropertyName is a PropertyName with other fields). I think inheritance is an easier and a more basic concept than ContentTypes and it fits to your needs.
NOTE: I remove some code like imports to simplify the code.
1) models.py file:
class PropertyName(models.Model):
name = models.CharField(max_length=20)
def __unicode__(self):
return self.name
class CustomPropertyName(PropertyName): # <-- Inheritance!!
user = models.ForeignKey(User)
def __unicode__(self):
return self.name
class SpecData(models.Model):
user = models.ForeignKey(User)
selection_title = models.CharField(max_length=20)
property = models.ForeignKey(PropertyName)
NOTES: The field SpecData.property points to PropertyName since all properties are saved in the PropertyName's database table.
2) forms.py file:
from django import forms
from django.db.models import Q
from models import SpecData, PropertyName
def UserSpecDataForm(user=None):
UserPropertiesQueryset = PropertyName.objects.filter(Q(custompropertyname__user=None) | Q(custompropertyname__user__id=user.id))
class SpecDataForm(forms.ModelForm):
property = forms.ModelChoiceField(queryset=UserPropertiesQueryset)
class Meta:
model = SpecData
exclude = ('user',)
return SpecDataForm
NOTES: The trick here is to generate the form SpecDataForm dynamically, by filtering properties according the user specified in the parameter.
3) views.py file:
from forms import UserSpecDataForm
def index(request):
if request.POST:
form = UserSpecDataForm(request.user)(request.POST) # instance=user
if form.is_valid():
spec_data = form.save(commit=False)
spec_data.user = request.user
spec_data.save()
else:
form = UserSpecDataForm(request.user)()
return render_to_response('properties.html', {'form': form}, context_instance=RequestContext(request))
NOTES: Nothing special here, just a call to form.UserSpecDataForm(request.user) that returns the form class and then instantiate. Also setted the logged-in user to the object returned on save since It was excluded in the form to not show in front-end.
Following this basic example you can do the same with formsets if you need it.
UPDATE:
Formset can be used by adding following code to the view:
user_specdata_form = UserSpecDataForm(user=request.user)
SpecdataFormSet = inlineformset_factory(User, SpecData, form=user_specdata_form, extra=30)
The complete project sample can be downloaded from http://ge.tt/904Wg7O1/v/0
Hope this helps
1a) have you looked into django's ContentType framework this will allow you to have generic foreign keys and you can put restrictions on what types of models are acceptable to store in.
1b) I think that the validation for accepting what type of foreign key is acceptable shouldn't be in your model but should be part of your form validation before saving.
2) If you do use a model form you're going to have to define your own custom widget for the propery field. This means you're probably going to have to write you're own render function to render the html from the field. You should also define your own validation function on the form to make sure that only the appropriate data is acceptable to save.
3) I don't think you'll have to do anything you aren't already doing in the views
Use GenericForeignKey:
class SpecData(models.Model):
user = models.ForeignKey(User)
selection_title = models.CharField(max_length=20)
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
property = GenericForeignKey('content_type', 'object_id')
You can use this to combine the two fields(type & id) into a single choice field.
One way is that you have only one model, make user nullable:
class PropertyName(models.Model):
user = models.ForeignKey(User, null=True, blank=True)
name = models.CharField(max_length=20)
class SpecData(models.Model):
user = models.ForeignKey(User)
selection_title = models.CharField(max_length=20)
property = ForeignKey(PropertyName)
So, if user is not set, it is a general property. If it is set, it is related to this user.
However, please note that if you need unique property names, that NULL != NULL.
Of course, the suggested GenericForeignKey solution is better for some cases.
Also, you can easily make the normal (non-model) form with that you describe and separate form logic from model logic.

Categories

Resources