Query a double foreign key and display in template - python

Models:
class Patient(models.Model):
patientID = models.CharField(max_length=200, unique=True, help_text='Insert PatientID')
birth_date = models.DateField(auto_now=False, auto_now_add=False, help_text='YYYY-MM-DD')
gender = models.CharField(max_length=200,choices=Gender_Choice, default='UNDEFINED')
class Examination(models.Model):
number_of_examination = models.IntegerField()
patient = models.ForeignKey(Patient, on_delete=models.CASCADE)
date_of_examination = models.DateField(auto_now=False, auto_now_add=False, help_text='YYYY-MM-DD')
class GeneralData(models.Model):
examination = models.ForeignKey(Examination, on_delete=models.CASCADE)
height = models.FloatField(default='-', help_text= '[m] not [cm]! ')
weight = models.FloatField(default='-', help_text= '[kg]')
aha_classification = models.IntegerField(choices=AHA_CHOICES, default=0)
My Problem:
I don't know how to query the general data object with the number of examination = 1 for one special patient. I want to display the object on the detail page of the patient. I can query on the Examination class without problems. But then I just don't know how to query the generaldata object. The detail page loads only the Patient model. Due to this I have to query from the Patient model over the Examination model to the Generaldata model right? Or is it possible to load other models in the template? Thanks for your help!
Got it!
Added to my DetailView:
def DetailView(generic.DetailView):
model = Patient
template_name = 'app/detail.html'
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super(DetailView, self).get_context_data(**kwargs)
# Add in a QuerySet
context['FirstGeneral'] = GeneralData.objects.filter(examination__number_of_examination=1, examination__patient=get_object_or_404(Patient, pk=self.kwargs.get('pk')))
return context

"The detail page loads only the Patient model (...) is it possible to load other models in the template ?"
You don't "load" models "in the template", you pass them (or any other object you want) to the template's context - most often from your view code but actually from whereever you want to render a template. And yes of course you can pass just whatever you want, populating the template context is up to you.
The query which will work is:
Generaldata.objects.filter(examination__number_of_examination=1, examination__patient=Testpatient)
but this is in the wrong order.
Why is it "in the wrong order" ??? And if that query works what prevents you from using it ?
NB : if you're using generic DetailView, adding extra context is documented here

Related

How to give multiple log-in's access to one keyword-based dataset in django app

Here is the scenario I am working on: I have django app that creates records which I call sessions:
blog.models.py
class Session(models.Model):
uid = models.CharField(max_length=50, blank=True)
cid = models.CharField(max_length=50, blank=True)
action_type = models.CharField(max_length=50, blank=True)
action_name = models.CharField(max_length=50, blank=True)
action_value = models.CharField(max_length=50, blank=True)
session_date = models.DateTimeField(auto_now_add=True)
client = models.CharField(max_length=50, blank=True)
I have a dashboard page to show charts and a database page to show the records as a table:
blog.urls.py
path('', auth_views.LoginView.as_view(template_name='users/login.html'), name='blog-home'),
path('<str:username>/dashboard/', views.dashboard and DashboardListView.as_view(), name='blog-dashboard'),
path('<str:username>/database/', views.database and SessionListView.as_view(), name='blog-database'),
So when you log in, my SessionListView.as_view() goes through the whole database and displays only those records where the Session.client == the url's 'username' value.
Example: when user: DummyCo logs in (www.website.com/DummyCo/database/) they see only Session records where the Session.client field is 'DummyCo.' This has worked out great so far.
But here is the problem: I now need to provide multiple logins to users to see the same dashboard and database page.
Example: jim#DummyCo.com and amy#DummyCo.com both need to see the DummyCo records, but if I provided them with their own logins then their username's in the url would not match and thus the DummyCo records would not show. I thought using the built-in django Groups would be a solution but that seems to only help with authentication and permissions on the backend. I also extended my user model with a Profile model:
users/models.py
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
group = models.ForeignKey(Group, blank=True, null=True, default=None, on_delete=models.SET_DEFAULT)
image = models.ImageField(default='default.jpg', upload_to='profile_pics')
user_client = models.CharField(max_length=50, blank=True, null=True, default=None)
def __str__(self):
return f'{self.user.username} Profile'
I made the user_client model field to try and connect the Profile (and thus User) with the Session.client field: instead of <str:username>/database/ I thought i'd be able to use <str:client_user>/database/ and simply fill that field with 'DummyCo' on both Jim and Amy's profile to give them access to the records.
I read in a couple of places that the key to handling this problem is to switch the user model from one-to-one to many-to-one type early or before i build out the app. Unfortunately I have already put a ton of work into this project. I also read that I should look at the built-in User model as more of an account and less of a user. So is there a simple way to give multiple users access to one User/account?
Also, here is the views:
blog/views.py
class SessionListView(LoginRequiredMixin, ListView):
model = Session, Profile
template_name = 'blog/database.html'
context_object_name = 'sessions'
ordering = ['-session_date']
paginate_by = 25
def get_queryset(self):
user = get_object_or_404(User, username=self.kwargs.get('username'))
return Session.objects.filter(client=user).order_by('-session_date')
def get_context_data(self, **kwargs):
user = get_object_or_404(User, username=self.kwargs.get('username'))
context = super().get_context_data(**kwargs)
context['distinct_campaigns'] = Session.objects.filter(client=user).values('cid').distinct().order_by('cid')
context['distinct_action_types'] = Session.objects.filter(client=user)\
.values('action_type')\
.distinct().order_by('action_type')
return context
# login_required()
def database(request):
context = {
'sessions': Session.objects.all()
}
return render(request, 'blog/database.html', context, {'title': 'Database'})
Okay I figured out a solution:
I thought I needed to do some trickery on the html file within the for loop showing my query set sessions but it turns out that can be adjusted in my views.py file. Before this update my views.py looked like this:
class SessionListView(LoginRequiredMixin, ListView):
model = Session, Profile
template_name = 'blog/database.html'
context_object_name = 'sessions'
ordering = ['-session_date']
paginate_by = 25
def get_queryset(self):
user = get_object_or_404(User, username=self.kwargs.get('username'))
return Session.objects.filter(client=user).order_by('-session_date')
def get_context_data(self, **kwargs):
user = get_object_or_404(User, username=self.kwargs.get('username'))
context = super().get_context_data(**kwargs)
context['distinct_campaigns'] = Session.objects.filter(client=user).values('cid').distinct().order_by('cid')
context['distinct_action_types'] = Session.objects.filter(client=user)\
.values('action_type')\
.distinct().order_by('action_type')
return context
I realized the def get_queryset(self) was grabbing the logged-in username, then reviewing the full database and adding all records with the same session.client value as the value of the logged in user (i.e. DummyCo). So to make this work for a user like 'DummyCo_Sally', I changed the logic in that def like so:
class SessionListView(LoginRequiredMixin, ListView):
# gets the actual user (i.e. DummyCo_Sally)
user = get_object_or_404(User, username=self.kwargs.get('username'))
# turns user to a string
user_string = str(user)
# designates the _ as the separator
sep = '_'
# strips off _ and everything after it
stripped_user = user_string.split(sep, 1)[0]
# establishes the queryset as 'DummyCo' even though 'DummyCo_sally' is logged in
return Session.objects.filter(client=stripped_user).order_by('-session_date')
I doubt this method is the best way of handling multiple users seeing one umbrella data set, but it did the trick for me. This method also likely creates a security risk for applications that have public-facing user registration. But it did the trick for me.

ManyToMany Relationship between two models in Django

I am trying to build a website that users can add the courses they are taking. I want to know how should I add the ManyToMany relationship. Such that we can get all users in a course based on the course code or instructor or any field. And we can also get the courses user is enrolled in. Currently, my Database structure is:
class Course(models.Model):
course_code = models.CharField(max_length=20)
course_university = models.CharField(max_length=100)
course_instructor = models.CharField(max_length=100)
course_year = models.IntegerField(('year'), validators=[MinValueValidator(1984), max_value_current_year])
def __str__(self):
return self.course_code
and my user model:
class Profile(AbstractUser):
bio = models.TextField()
image = models.ImageField(default='defaults/user/default_u_i.png',
courses = models.ManyToManyField('home.Course',related_name='courses')
def __str__(self):
return self.username
I was wondering should ManyToMany relationship be in User model or the course model? Or will it make any difference at all?
EDIT: For adding course to post object now I am using this view but it seems to not work:
#login_required
def course_add(request):
if request.method == "POST":
form = CourseForm(request.POST or none)
if form.is_valid():
course = form.save()
request.user.add(course)
else:
form = CourseForm
context = {
'form':form
}
return render(request,'home/courses/course_add.html', context)
For a relational databases, the model where you define the ManyToManyField does not matter. Django will create an extra table with two ForeignKeys to the two models that are linked by the ManyToManyField.
The related managers that are added, etc. is all Django logic. Behind the curtains, it will query the table in the middle.
You however need to fix the related_name=… parameter [Django-doc]. The related_name specifies the name of the relation in reverse so from Course to Profile in this case. It thus should be something like 'profiles':
class Profile(AbstractUser):
bio = models.TextField()
image = models.ImageField(default='defaults/user/default_u_i.png',
courses = models.ManyToManyField('home.Course', related_name='profiles')
def __str__(self):
return self.username
You thus can obtain the people that particiate in a Course object with:
mycourse.profiles.all()
and you can access the courses in which a Profile is enrolled with:
myprofile.courses.all()
For more information, see the Many-to-many relationships section of the documentation.
You can add a course to the courses of a user with:
#login_required
def course_add(request):
if request.method == 'POST':
form = CourseForm(request.POST)
if form.is_valid():
course = form.save()
request.user.courses.add(course)
else:
form = CourseForm()
context = {
'form': form
}
return render(request,'home/courses/course_add.html', context)
You don't need to add the related name. Default is "courses_set" in your case.
Here is excerpt from: https://docs.djangoproject.com/en/dev/topics/db/queries/#backwards-related-objects
Following relationships “backward” If a model has a ForeignKey,
instances of the foreign-key model will have access to a Manager that
returns all instances of the first model. By default, this Manager is
named FOO_set, where FOO is the source model name, lowercased. This
Manager returns QuerySets, which can be filtered and manipulated as
described in the “Retrieving objects” section above.

Django Form: Only show manytomany objects from logged in user

The current problem is that my form shows the logged in user all Portfolios ever created. The form should only show portfolios that the logged-in user created.
Something like this:
associated_portfolios manytomany field = ...objects.filter(user=user_id)
I'm not sure if this should be implemented in the forms.py or views.py and if so how. I've been going through the django documentation and found 'formfield_for_manytomany' but not sure if this is only meant for admin.
Models.py
class Portfolio(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
name = models.CharField(max_length=20)
description = models.CharField(max_length=250, blank=True, null=True)
class Post(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=50)
body = RichTextUploadingField(blank=True, null=True)
associated_portfolios = models.ManyToManyField(Portfolio, blank=True)
created_on = models.DateField(auto_now_add=True, editable=False)
Views.py
class PostCreate(CreateView):
model = Post
form_class = PostCreateForm
def formfield_for_manytomany(self, db_field, request, **kwargs):
self.fields['associated_portfolios'] = Portfolio.objects.filter(user=self.request.user)
return super().formfield_for_manytomany(db_field, request, using=self.using, **kwargs)
forms.py
class PortfolioCreateForm(ModelForm):
class Meta:
model = Portfolio
fields = ['user', 'name', 'description']
class PostCreateForm(ModelForm):
class Meta:
model = Post
fields = ['user', 'title', 'body', 'category', 'associated_portfolios']
Since you're using a ModelForm, the associated_protfolios field will be a ModelMultipleChoiceField [docs]. This field has a queryset attribute [docs]. We want to modify that attribute.
Django's CreateView has a method get_form, which in this case will grab your PostCreateForm. This is a good spot to filter the field's queryset, since we have access to the user:
class PostCreate(CreateView):
model = Post
form_class = PostCreateForm
def get_form(self, *args, **kwargs):
form = super().get_form(*args, **kwargs) # Get the form as usual
user = self.request.user
form.fileds['associated_portfolios'].queryset = Portfolio.objects.filter(user=user)
return form
Did you try this
self.fields['associated_portfolios'] = Post.objects.filter(associated_portfolios__portfolio__user=request.user)
OR
user_posts = Post.objects.filter(user=request.user)
self.fields['associated_portfolios'] = user_posts.associated_portfolios.all()
read more about M2M relationships querying here, because I think your problem may be with it.
Also, I'm not sure about your actual data maybe it's right and it gives a correct result as filtering Portfolio model against current user to get its objects looks right for me, but anyway double check everything again.
And as a final note, add related_name to your model fields so you can use it easily for reverse relations rather than going with Django's default naming, it will be clearer and give a better understanding.

How to save choices value in django ?

I am donig with a poll system for my class. I use model-form and create-view to serve the poll form. I use choices in in the field but I just find out that create-view only save the last value of the checkboxes and I want to save all the selected choices as a list maybe. I've tried to change the form_valid() method, but I just find out that I need to iterate all the fields to check wheather there are multipule choices. It's not flexible. And I can't figure out other solutions...
How can I meet this requirement? I am truly a newbie..
Thanks in advance.
Thank the friend below for replying in such a short interval after I raised my question. Here is my code.
models.py
CHOICES = (('m','Math'),('f','French'),('s','Science'),('l','literature'))
class Poll(models.Model):
[...]
subject = models.CharField(max_length = 5,choices = CHOICES, blank=True)
[...]`
forms.py
class PollForm(forms.ModelForm):
model = Poll
fields = [..., 'subject', ...]
widgets = {'subject':forms.CheckboxSelectMultiple}
views.py
class PollView(CreateView):
form_class = PollForm
template_name = 'poll.html'
Students can choose subjects they want.
It seems like you need to convert your model. If you could provide a sample of the structure that you are using it would be helpful. Still lets try solving your query. First you need identify that choices is nothing more than a many to many field. Saving it in the db should be a bit easier that way. Lets try taking an example with choices for a user:
class Choices(models.Model):
description = models.CharField(max_length=100)
class UserProfile(models.Model):
user = models.ForeignKey(User, blank=True, unique=True, verbose_name='profile_user')
choices = models.ManyToManyField(Choices)
def __unicode__(self):
return self.name
Now if you want to make a default form you could simply do something like:
class ProfileForm(forms.ModelForm):
Meta:
model = UserProfile
Now comes your main view. This can be editted and rendered to whatever your use case demands it to be:
if request.method=='POST':
form = ProfileForm(request.POST)
if form.is_valid():
profile = form.save(commit=False)
profile.user = request.user
#Implement this as a pre-save so that you can add additional value
profile.save()
else:
form = ProfileForm()
Hope this helps.

A 'related content' list with DetailView in Django

Based on an "Article" model, I'm trying to display a "related content" list in the template by filtering its model field named "category". This "category" field has a ManyToMany relationship to another model named "Category".
It's looks like a very simple task but I can't figure out how to achieve my purpose. By now, a list could be displayed but seems nothing was filtered out.
Below is my DetailView class with a "get_context_data()" method which can product a template tag for displaying a list. Apparently the "F()" class is not the solution.
class ArticleDetail(generic.DetailView):
model = Article
template_name = 'article/detail.html'
def get_context_data(self, **kwargs):
context = super(ArticleDetail, self).get_context_data(**kwargs)
context_related = Article.objects.filter(F('category')).distinct()
context['related'] = context_related
return context
Besides, I also tried to filter with arguments like "category" and "category__exact=F('category')" but still failed.
And here are the models (simplified for question):
class Article(models.Model):
title = models.CharField(max_length=100)
content_text = models.TextField()
category = models.ManyToManyField('Category', blank=True)
def __unicode__(self):
return self.title
class Category(models.Model):
title = models.CharField(max_length=100, unique=True)
def __unicode__(self):
return self.title
No, that's not what F() is for at all.
You don't explain exactly what you do want though. I presume you're looking for other articles in the same categories as the current article. That's easy enough:
Article.objects.filter(category__in=self.object.categories.all())

Categories