Django: combine items from two queries (two different Models) together - mysql

I'm building an event platform with Django with some events public and some invite-only. Here's how it basically works:
Every event is based on the Event model.
Private events are only viewable by invited guests: this is achieved
through an Invite model referencing both the event and the guest.
Users can inform whether they will be attending an event or not
through an Answer model that stores the associated event, user
and the answer.
The Answer and Invite models are totally independent from each other, that way I am able to use the Answer model for both public and private events.
What I'm trying to achieve:
For each event where I'm invited, display the invitation (event.creator invited you to event.name) and, if it exists, my associated answer, else display a form to answer.
So I think what I'm trying to do is getting all events where I'm invited (I have no problem with that) and joining that to my answer (where user=me). The problem is that the answer might not even exist yet --if I haven't answered.
Ideally, I would have all of that in one single query so that I could do something like this in the templates: invited_event.answer and display the answer if it exists.
EDIT:
So I think what I need ultimately is to mix two queries: one that gets all the events where I'm invited (1) and an other that grabs all answers for those events (2).
(1)
latest_events_invited = Event.objects.filter(invite__user_invited=request.user)
(2)
answers_events_invited = Answer.objects.filter(user_answering=request.user, event__in=latest_events_invited)
Something like: for each event in latest_events_invited, append corresponding answer from answers_events_invited.
Any ideas?
Code:
My template (index.html):
<h3>Invites</h3>
{% if latest_invites_list %}
<ul>
{% for event in latest_events_invited %}
<li>
{{ event.creator }} invited you to {{ event }}<br/ >
<!--IDEALLY:-->
{% if event.answer %}
You answered: {{ answer.answer }}
{% else %}
<form action="{% url 'events:answer_event' invite.event.id %}" method="post">
{% csrf_token %}
Answer:
<select name="answer">
<option value="1" >Attending</option>
<option value="2" >Not attending</option>
</select>
<input type="submit" name="submit" value="Answer">
</form>
{% endif %}
</li>
</ul>
{% else %}
<p>No invites.</p>
{% endif %}
The view (views.py)
def index(request):
if request.user.is_authenticated():
latest_events_invited = Event.objects.filter(invite__user_invited=request.user)
latest_answers_list = Answer.objects.filter(user_answering=request.user, event__in=latest_events_invited)
#do something with those to get: "latest_events_invited_with_answers"
context = {'latest_events_invited':latest_events_invited, 'latest_answers_list':latest_answers_list}
else:
[...]
return render(request, 'events/index.html', context)
And the models.
Event
class Event(models.Model):
PRIVACY_CHOICES = (
(0, 'Public'),
(1, 'Invite only'),
)
name = models.CharField(max_length=200)
[...]
privacy = models.PositiveSmallIntegerField(max_length=1, choices=PRIVACY_CHOICES, default=0)
invited = models.ManyToManyField(settings.AUTH_USER_MODEL, through='Invite', related_name='events_invited', blank=True)
answers = models.ManyToManyField(settings.AUTH_USER_MODEL, through='Answer', related_name='events_answered', blank=True)
Invite
class Invite(models.Model):
user_invited = models.ForeignKey(settings.AUTH_USER_MODEL)
event = models.ForeignKey(Event)
created = models.DateTimeField(default=timezone.now, editable=False)
class Meta:
unique_together = (("user_invited", "event"),)
Answer
class Answer(models.Model):
ANSWER_CHOICES = (
(1, 'Attending'),
(2, 'Not attending'),
)
user_answering = models.ForeignKey(settings.AUTH_USER_MODEL)
event = models.ForeignKey(Event)
answer = models.PositiveSmallIntegerField(max_length=1, choices=ANSWER_CHOICES)
class Meta:
unique_together = (("user_answering", "event"),)
Hopefully someone here can help me out.
Thanks.

One option is to automatically add an Answer whenever someone is invited, with a default of No. Use the post_save signal of Invite or overwrite the save method so you don't have to create an associated Answer each time.
Another option, probably better, is to do the logic in the view rather than in the template. Check if an answer exists; if it does, pass a list of answers to the template; if not, pass an empty list.
EDIT: Or, better yet:
In the view:
try:
user_answer = Answer.objects.filter(user_answering = request.user).filter(event=event)
except ObjectDoesNotExist:
answer = None
EDIT2: Or how about:
Answer.objects.filter(user_answering = request.user).filter(event__in=Event.objects.filter(user__in = event.invited))

Ok, so I figured it out. I don't know if this is the optimal solution but it works for now.
In the end, it is only 2 queries, which I combine with for loops.
Here's the code: (views.py)
def index(request):
if request.user.is_authenticated():
latest_events_invited = Event.objects.filter(invite__user_invited=request.user)
latest_answers_for_events = Answer.objects.filter(user_answering=request.user, event__in=latest_events_invited)
for event in latest_events_invited:
for answer in latest_answers_for_events:
if answer.event.id == event.id:
event.answer = answer.answer
else:
event.answer = ''
context = {'latest_events_invited': latest_events_invited,}
return render(request, 'events/index.html', context)
I can then access the answer directly in the template (index.html) like so:
{{ event.answer }}

Related

Django How to make the condition correct on an HTML page - search bar

I try to do a search engine if the word in my DB thah I created then display the word on the HTML page and if not then nothing.. I did it right in VIEW but I can not apply it on the HTML page I searched the internet and did not find an answer I'm sure I fall for something stupid.
This is the view
def Search_word(request):
search = request.POST.get("search") #Grab the search item
return render(request,"search_page.html", {"search":search})
this is the html:
{%for i in Word.English_word%}
{%if search in Word.English_word%}
{{search}}
{%endif%}
{%endfor%}
and the urls:
path("Search_page",views.Search_word ,name="Search-page"),
models:
class Words(models.Model):
English_word = models.CharField(max_length=30)
Hebrew_word = models.CharField(max_length=30)
How_To_Remember = models.CharField(max_length=40)
Name = models.CharField(max_length=20)
pub_date = models.DateTimeField()
The problem is that even if the word is valid it does not show me anything ..
You should implement the filtering logic in the view, not in the template. Templates are for rendering logic, not business logic. Furthermore one should filter with the database, since databases are designed to do this.
The view thus looks like:
def Search_word(request):
search = request.POST.get('search')
items = Word.objects.filter(English_word__contains=search)
return render(
request,
'search_page.html',
{'search': search, 'items': items}
)
and then in the template we render this with:
{% for item in items %}
{{ item.English_word }}: {{ item.Hebrew_word }} <br>
{% endfor %}
You can use as lookup __contains to check if the English_word has a substring that is equal to search, with __icontains you check case-insensitive, with __iexact you look for Words that match search case-insensitive, and finally you can filter with Engish_word=search for an exact match.

How can I convert django forms.py select items to a list in html?

I have a form with which I intend to capture student marks. I have an issue like I don't know the best way to put it so that I have student name and markfield beside it for all the students. calling {{ form }} brings the form with select dropdown items(not what I want). Specifying form fields do not populate anything.i.e
{% for field in form %}
{{ field.student }}
{{ field.marks }}
{% endfor %}
This is the form
class AddMarksForm(forms.ModelForm):
def __init__(self, school,klass,term,stream,year, *args, **kwargs):
super(AddMarksForm, self).__init__(*args, **kwargs)
self.fields['student'] = forms.ModelChoiceField(
queryset=Students.objects.filter(school=school,klass__name__in=klass,stream__name=stream[0]))
class Meta:
model = Marks
fields = ('student','marks')
This is how I tried the html rendering
<form method="post" enctype="multipart/form-data" action="{% url 'add_marks' %}">
{% csrf_token %}
{% if form %}
<table>
<tr>
<td>Student</td>
<td>Marks</td>
</tr>
{% for field in form %}
<tr>
<td>{{ field.student }}</td>
<td>{{ field.marks }}</td>
</tr>
</table>
{% endfor %}
<button class="btn btn-outline-primary">Save</button>
</form>
My models.
class Year(models.Model):
year = models.IntegerField(validators=[MinValueValidator(2018), MaxValueValidator(4000),])
year_term = models.ForeignKey('exams.Term',on_delete=models.SET_NULL,related_name='year_term', null=True,blank=True)
class Subject(models.Model):
name = models.CharField(max_length=50)
teacher = models.ForeignKey(TeacherData,on_delete=models.CASCADE)
students = models.ManyToManyField(Students)
class Term(models.Model):
year = models.ForeignKey(Year,on_delete=models.CASCADE)
name = models.CharField(max_length=30)
exam_term = models.ForeignKey('exams.Exam',on_delete=models.SET_NULL,null=True,blank=True,related_name='exam_term')
class Exam(models.Model):
school = models.ForeignKey(School,on_delete=models.CASCADE)
year = models.ForeignKey(Year,on_delete=models.SET_NULL, null=True)
term = models.ForeignKey(Term,on_delete=models.SET_NULL, null=True)
name = models.CharField(max_length=20)
klass = models.ManyToManyField("students.Klass", related_name='klass',null=True,blank=True)
class Marks(models.Model):
exam = models.ForeignKey(Exam,on_delete=models.SET_NULL,null=True,blank=True)
subject = models.ForeignKey(Subject,on_delete=models.SET_NULL,null=True,blank=True)
student = models.ForeignKey(Students,on_delete=models.SET_NULL,null=True,blank=True)
marks = models.IntegerField(validators=[MinValueValidator(0), MaxValueValidator(100),] ,null=True,blank=True)
More models
class Klass(models.Model):
name = models.IntegerField(validators=[MinValueValidator(1), MaxValueValidator(4),], help_text='E.g 1,2,3, 4')
school = models.ForeignKey(School,on_delete=models.CASCADE)
exam = models.ForeignKey("exams.Exam",on_delete=models.CASCADE, related_name='exam',null=True,blank=True)
class Stream(models.Model):
name = models.CharField(max_length=50,help_text='Name of the stream')
klass = models.ForeignKey(Klass,on_delete=models.CASCADE,help_text='Choose a class the stream belongs to')
class Students(models.Model):
id = models.AutoField(primary_key=True)
#student_user = models.OneToOneField(User, on_delete=models.CASCADE,null=True,blank=True)
school = models.ForeignKey(School,on_delete=models.CASCADE,help_text='A school must have a name')
adm = models.IntegerField(unique=True,validators=[MinValueValidator(1), MaxValueValidator(1000000),],help_text="Student's admission number" )
name = models.CharField(max_length=200,help_text="Student's name")
kcpe = models.IntegerField(validators=[MinValueValidator(100), MaxValueValidator(500),], help_text='What the student scored in primary')
klass = models.ForeignKey(Klass, on_delete=models.CASCADE,null=True,help_text="Student's Class/Form")
stream = models.ForeignKey(Stream,on_delete=models.CASCADE,blank=True,null=True,help_text="Student's Stream")
gender = models.ForeignKey(Gender,on_delete=models.CASCADE,null=True,help_text="Student's Gender")
notes = models.TextField(blank=True,null=True,help_text='Anything to say about the student. Can always be edited later')
Here is my suggestion based on the discussion in question comments.
# models.py
class User(models.Model):
is_teacher = models.BoolField(default=False)
is_student = models.BoolField(default=False)
class Subject(models.Model):
taught_by = models.ForeignKey(User, on_delete=models.PROTECT, limit_choices_to={"is_teacher": True})
students = models.ManyToManyField(User, limit_choices_to={"is_student": True})
class Exam(models.Model):
subject = models.ForeignKey(Subject, on_delete=models.CASCADE)
class Marks(models.Model):
exam = models.ForeignKey(Exam, on_delete=models.CASCADE)
student = models.ForeignKey(User, on_delete=models.CASCADE, limit_choices_to={"is_student": True})
marks = models.IntegerField(validators=[MinValueValidator(0), MaxValueValidator(100),], null=True, blank=True)
# no need for a subject field here, that is linked through the Exam object
class Meta:
unique_togetger = [('exam', 'student'),]
I have only written the important fields, you can add more fields that you need.
# views.py
class AddMarks(views.View):
def get(self, request, exam_id):
try:
exam = Exam.objects.get(id=exam_id)
except Exam.DoesNotExist:
# some kind of handler that returns (important!)
context = {
"students": exam.subject.students.all()
"current_marks": Marks.objects.filter(exam=exam)
}
return render("add_marks.html", context=context)
def post(self, request, exam_id):
try:
exam = Exam.objects.get(id=exam_id)
except Exam.DoesNotExist:
# some kind of handler that returns (important!)
for student in exam.subject.students.all():
marks = int(request.POST.get(f"marks_{student.username}", None))
if marks:
# if that particular marks field exists in POST and is filled, let's update (if it exists) or create a new marks object
marks_object = Marks.objects.filter(exam=exam, student=student).first() or Marks(exam=exam, student=student)
marks_object.marks = marks
marks_object.save()
return self.get(request, exam_id)
<!--add_marks.html-->
<form method="POST">
<table>
{% for student in students %}
<tr>
<td>{{ student.get_full_name }}</td>
<td><input type="number" name="marks_{{ student.username }}" /></td>
</tr>
{% endfor %}
{% if students %}
<tr><td colspan="2"><input type="submit" value="Save marks" /></td></tr>
{% else %}
<tr><td colspan="2">No student is taking this class</td></tr>
{% endif %}
</table>
</form>
{% if current_marks %}
<h3>Currently saved marks</h3>
<table>
{% for marks in current_marks %}
<tr>
  <td>{{ marks.student.get_full_name }}</td>
<td>{{ marks.marks }}</td>
</tr>
{% endfor %}
</table>
{% endfor %}
I did make a couple of changes. Most noticeable is the Student model, or rather the lack thereof. I have made the User flaggable as either student or teacher, allowing your students to log in and see their grades or perhaps sign up for classes.
Second, as mentioned in the code, there is no need for a subject field in the Marks model, as that data can be pulled from the Exam itself.
Lastly about what this does. It will create a form with a table in it, where each row will contain a field named marks_<username>. The field can then be retrieved in the post method handler. If the field is there and has a value, the view will either update existing, or create a new Marks model. If there are already any marks saved, they will be showed below in a separate table.
I have removed the form entirely as that is redundant in this case. If you really wanted to, somewhere else perhaps, this multiple-forms-on-one-page thing can be achieved by using Django's FormSet API. The documentation about that is [here][1]. That allows you to stack many forms of the same kind one after another, for editing many rows at once. But since we only really have one field, I find it easier to just handle that one field myself.
One last thing, I did not write any error checking and handling. That I will leave to you. Potential places that need treatment are the two except blocks, that need to return from the method so that the code below isn't executed. Some nice 404 page should suffice. Another place is the int() call around getting the POST contents. That might throw an exception if something non-integer is sent in. You should be able to handle that with a simple try-except block.

How do I use a for loop to reuse a django form in a template

After struggling with this issue for a while, I am hoping someone here can point me in a more productive direction.
I am trying to take an indeterminate number of variables in a database (obtained through a different template) and render them on a webpage, each variable with a simple data entry form, to be saved back to the database. Basically, it's a tracker for analysis. Say I want to track my daily sleep, running time, and calorie intake (the variables). I have those saved in a database as variables and want to call upon those variables and show them on a webpage with a daily entry form. I am using a "for" loop right now and it renders the way I want it to, with the variable name and the form, but it is only saving the last item in the variable list. How do I amend the code below such that when I hit the save button for each form rendeded, it saves the information for that variable (not just the last one rendered).
Below is the code. Any and all help would be infinitely appreciated.
Models...
class Variable(models.Model):
date_added = models.DateTimeField(auto_now_add=True)
created_by = models.ForeignKey(get_user_model(), default='', on_delete=models.CASCADE) # id the active user
ENTRY_TYPE_CHOICES = [
('numeric', 'enter a daily number'),
('scale', 'rate daily on a scale of 1-10'),
('binary', "enter daily, 'yes' or 'no' "),
]
variable = models.CharField(max_length=50, default='')
entry_type = models.CharField(max_length=50, choices=ENTRY_TYPE_CHOICES, default="numeric")
def __str__(self):
return self.variable
class DailyEntry(models.Model):
date = models.DateField(default=datetime.date.today)
# date_added = models.DateTimeField(auto_now_add=True)
created_by = models.ForeignKey(get_user_model(), default='', on_delete=models.CASCADE) # id the active user
variable_name = models.CharField(max_length=50, default='')
variable_id = models.SmallIntegerField(default=0000)
entry = models.FloatField(default=9999)
class Meta:
verbose_name_plural = 'Daily Entries'
def __str__(self):
return self.variable
Form...
class VariablesForm(forms.ModelForm):
class Meta:
model = Variable
fields = ['variable', 'entry_type' ]
labels = {'variable':'Dependent variable to track', 'entry_type': 'Type of measure'}
class DailyEntryForm(forms.ModelForm):
class Meta:
model = DailyEntry
fields = ['variable_name', 'variable_id', 'entry', 'date']
labels = {'entry': 'Daily entry', 'date': 'Date'}
widgets = {'variable_name': forms.HiddenInput(), 'variable_id': forms.HiddenInput()}
Views...
def daily_entry(request):
''' page to make daily entries '''
vars = Variable.objects.filter(id__gt = 0 )
if request.method != 'POST':
# No data submitted. GET submitted. Create a blank form
form = DailyEntryForm()
else:
#POST data submitted. Process data
form = DailyEntryForm(data=request.POST)
if form.is_valid():
data = form.save(commit=False)
data.created_by = request.user
data.save()
return HttpResponseRedirect(reverse('entry_new'))
context = {'form': form, 'vars': vars}
return render(request, 'entry_new.html', context)
and HTML...
{% extends 'base.html' %}
{% load crispy_forms_tags %}
{% block content %}
{% for var in vars %}
<div>
<ul>
<h3>{{ var.variable }}</h3>
<form class="" action="" method="post">
{% csrf_token %}
{{ form|crispy }}
<input type="hidden" name="variable_id" value="{{ var.id }}" >
<input type="hidden" name="variable_name" value="{{ var.variable }}">
<input type="submit" name="" value="Save" />
</ul>
</div>
{% endfor %}
{% endblock content %}
Any help, well, helps...
Thanks!

django - change list in POST request

I have a tuple list and wanted to delete or add tuples in it depending on what button has been pressed. Adding tubles is functioning fine but my problem is, that for some reason if Im clicking on the button to delete a tuple, the list resets back the time to the state before the delete happened.
For example I have a list:
ctestformat = [('sung', 4, 15), ('ren', 3, 27), ('lexe', 4, 39)]
after deleting the number 15 I get:
ctestformat = [('ren', 3, 27), ('lexe', 4, 39)]
But after getting another POST request to delete or add, the list resets to the first state as if nothing got deleted
Here is my view to add and delete tuple depending on which button was clicked:
def editorstart(request, ctestformat=[]):
if request.method == 'POST':
"""If clicked on create gap button, create a new gap and put it in ctestformat"""
if 'create_gap' in request.POST:
selectedgap = request.POST['sendgap']
startindex = int(request.POST['startpoint'])-13
ctestformat.append((selectedgap, len(selectedgap), startindex))
ctestformat.sort(key=operator.itemgetter(2))
"""if clicked on deletegap, delete the gap from ctestformat"""
elif 'deletegap' in request.POST:
deleteindex = request.POST['deletegap']
test = [t for t in ctestformat if t[2] != int(deleteindex)]
ctestformat = test
# This function doesnt change anything to ctestformat
modifiedtext = createmodifiedtext(ctestformat)
return render(request, 'editor_gapcreate.html', {"text": modifiedtext, 'ctestformat': ctestformat})
If you have any other questions, just ask :)
EDIT:
added return in my view
my template:
{% extends "base_generic2.html" %}
{% block content %}
<form action="editorgapcreate" id=create method="POST">
<input type="hidden" name="sendgap" id="sendgap">
<input type="hidden" name="startpoint" id="startpoint">
<script src="../static/textselector.js"></script>
<div id="thetext" onmouseup="getSelectionText()">
<h1>{{ text|safe }}</h1>
</div>
{% csrf_token %}
<p></p>
<b>Your current selected gap:</b>
<p id="currentgap"></p>
<input type="hidden" name="text" id="text" value="{{ text }}">
<button type="submit" name="create_gap" id="gapcreate">Create gap</button>
</form>
{% endblock %}
Using a mutable value for a default argument in Python (a list in this case) is not normally a good idea. The list is created once when the function is defined, which means any changes you make to it are visible in subsequent function invocations. However, it seems as though this may be intended in your case.
The reason why you're not seeing the list change, is that the assignment you're making ctestformat = test after filtering out an item has no effect. You need to mutate the original list rather than reassigning, by first finding the index of the item within that list, and then using pop() to remove it. For example:
elif 'deletegap' in request.POST:
deleteindex = request.POST['deletegap']
for i, t in enumerate(ctestformat):
if t[2] == int(deleteindex):
ctestformat.pop(i) # Modify original list
break
...
I would still recommend not using a mutable default argument to achieve this. If you need to share data across requests, you'd be better to use a cache or a database, or possibly session state, depending upon your application requirements.

Django 'ManagementForm data is missing or has been tampered with' when saving modelForms with foreign key link

I am rather new to Django so this may be an easy question. I have 2 modelForms where there is a ForeignKey to another. My main goal is to save Indicators with a link to Disease (FK), such that for a particular disease, you can have multiple indicators.
With the code below, I get an error when I hit submit that says 'ManagementForm data is missing or has been tampered with'. Also, the code in views.py does not seem to be validating at the 3rd 'if' statement where there is a return HttpResponseRedirect. However, when I check my database, the values from the form have been written. Any ideas on why the error has been raised? and how to fix it?
My code is below:
models.py
#Table for Disease
class Disease(models.Model):
disease = models.CharField(max_length=300)
#Tables for Indicators
class Indicator(models.Model):
relevantdisease = models.ForeignKey(Disease)
indicator = models.CharField(max_length=300)
forms.py
class DiseaseForm(forms.ModelForm):
class Meta:
model = Disease
class IndicatorForm(forms.ModelForm):
class Meta:
model = Indicator
DiseaseFormSet = inlineformset_factory(Disease,
Indicator,
can_delete=False,
form=DiseaseForm)
views.py
def drui(request):
if request.method == "POST":
indicatorForm = IndicatorForm(request.POST)
if indicatorForm.is_valid():
new_indicator = indicatorForm.save()
diseaseInlineFormSet = DiseaseFormSet(request.POST, request.FILES, instance=new_indicator)
if diseaseInlineFormSet.is_valid():
diseaseInlineFormset.save()
return HttpResponseRedirect('some_url.html')
else:
indicatorForm = IndicatorForm()
diseaseInlineFormSet = DiseaseFormSet()
return render_to_response("drui.html", {'indicatorForm': indicatorForm, 'diseaseInlineFormSet': diseaseInlineFormSet},context_instance=RequestContext(request))
template.html
<form class="disease_form" action="{% url drui %}" method="post">{% csrf_token %}
{{ indicatorForm.as_table }}
<input type="submit" name="submit" value="Submit" class="button">
</form>
You have neither diseaseFormSet nor diseaseFormSet's management form in your template, yet you try to instantiate the formset. Formsets require the hidden management form which tells django how many forms are in the set.
Insert this into your HTML
{{ diseaseFormSet.as_table }}
{{ diseaseFormSet.management_form }}