Render multiple models with pk in a single view - django

I'm trying to pass lesson.price, and lesson.invoice_id from Lesson model and student.student_id from Student Model into the single view so that I can display them in a template.
However, Lesson model has a field "student" which has a foreign key to User, not to Student model. You will see my code for view class is wrong since I have no clue how to get a proper student object with a primary which is used for lesson object.
How could I get a proper student object with lesson_id primary key in view class?
class User(AbstractUser):
'''User model for authentication and lessons authoring.'''
class Role(models.TextChoices):
ADMIN="ADMIN",'Admin'
STUDENT="STUDENT",'Student'
TEACHER="TEACHER",'Teacher'
id = models.AutoField(primary_key=True)
username = models.CharField(
max_length=30,
unique=True,
validators=[RegexValidator(
regex=r'^#\w{3,}$',
message='Username must consist of # followed by at least three alphanumericals.'
)]
)
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
email = models.EmailField(unique=True, blank=False)
gender = models.CharField(max_length=255)
address = models.TextField(default='')
baseRole = Role.ADMIN
role = models.CharField(max_length=50, choices=Role.choices)
created_at = models.DateTimeField(default=timezone.now, blank=True)
updated_at = models.DateTimeField(default=timezone.now, blank=True)
def save(self, *args, **kwargs):
self.role = self.baseRole
return super().save(*args, **kwargs)
def __str__(self):
return self.first_name+" "+self.last_name
class Student(User):
student_id = models.CharField(max_length=10, default=uuid.uuid4)
baseRole = User.Role.STUDENT
student = StudentManager()
class Lesson(models.Model):
lesson_id = models.AutoField(primary_key=True)
lesson_name = models.CharField(max_length=255)
student = models.ForeignKey(User,on_delete=models.DO_NOTHING,related_name='studying', unique=True)
teacher = models.ForeignKey(User,on_delete=models.CASCADE, related_name='teaching')
start_time = models.DateTimeField(default=timezone.now)
interval = models.IntegerField()
duration = models.IntegerField()
created_at = models.DateTimeField(default=timezone.now, blank=True)
updated_at = models.DateTimeField(default=timezone.now, blank=True)
is_request = models.BooleanField()
number = models.IntegerField(default=0)
invoice_id = models.CharField(max_length=10, default=uuid.uuid4)
#property
def price(self):
return self.duration/5 * self.number
#staticmethod
def durations():
return [20, 30, 40, 45, 60]
#staticmethod
def subjects():
return ['Guitar','Violin','Piano', 'Voice', 'Cello','Ukulele','Recorder', 'Drums']
#staticmethod
def intervals():
return [2, 5, 7, 10, 14]
def __str__(self):
return "Lesson id: "+str(self.lesson_id)+", Student id: "+str(self.student.id)+", Student: "+str(self.student)
views.py
def invoice(request, lesson_id):
lesson = Lesson.objects.get(lesson_id=lesson_id)
student = Lesson.student.get(student_id=lesson.student.student_id)
return render(request, 'invoice.html', {'lesson':lesson, "student":student})
invoice.html
{% extends 'student/student_home_base.html' %}
{% block student_content %}
<head>Your Invoice</head>
<p>Your invoice reference number is{{lesson.student_id}}-{{lesson.invoice_id}}</p>
<p>Your Total Payable is {{lesson.price}}</p>
<p>Once you're done paying, please click this button below.</p>
<input type="submit" value="submit">
{% endblock %}

lesson = Lesson.objects.get(lesson_id=lesson_id) # you get the Lesson object
student = lesson.student # call the student attribute on the variable "lesson" not the Class Lesson
Note that the variable lesson contains an instance of the Lesson class.
Note that the variable student will actually contain an instance of the User class.
If you want the Student instance you should do:
student = lesson.student.student
where lesson in the Lesson object, the first .student calls the foreignkey relationship with User and the second .student is the relative Student object related by an OneToOne implicit relationship (with User) thanks to the multi-table inheritance.
all details here.

Related

How to use foreign key field's attribute for another model field

I have two models in different apps like so:
class Account(models.Model):
"""
Class to store fiat account information of a companies bank account
"""
number = models.CharField(max_length=100)
currency = models.ForeignKey(FiatCurrency, on_delete=models.CASCADE)
owner = models.ForeignKey(Company, on_delete=models.CASCADE)
date_added = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.number
class FiatTransaction(models.Model):
"""
Class to store Transactions made between escrow and operative white-listed fiat accounts
"""
debit_account = models.ForeignKey('company.Account', on_delete=models.CASCADE, related_name='debit_account')
credit_account = models.ForeignKey('company.Account', on_delete=models.CASCADE, related_name='credit_account')
executed_on = models.DateTimeField(auto_now_add=True)
amount = models.FloatField()
currency = debit_account.currency
is_processed = models.BooleanField(default=False)
fee = models.FloatField()
memo = models.CharField(max_length=250)
def __str__(self):
return F"Transferred {self.amount} from {self.debit_account} to {self.credit_account} at {self.executed_on}"
Now the field currency of model FiatTransaction doesn't seem to work the way I intend it to do. It raises
AttributeError: 'ForeignKey' object has no attribute 'currency'
# Source model
class FiatCurrency(models.Model):
"""
A model to store Fiat Currencies offered by Finchin to
include into cash-pools.
"""
ISO_Code = models.CharField(max_length=3)
name = models.CharField(max_length=50)
is_active = models.BooleanField(default=True)
def __str__(self):
return self.name
Why's that and how to make this work?
You can make a #property that will determine the currency of that object with:
class FiatTransaction(models.Model):
debit_account = models.ForeignKey('company.Account', on_delete=models.CASCADE, related_name='debit_account')
credit_account = models.ForeignKey('company.Account', on_delete=models.CASCADE, related_name='credit_account')
executed_on = models.DateTimeField(auto_now_add=True)
amount = models.FloatField()
is_processed = models.BooleanField(default=False)
fee = models.FloatField()
memo = models.CharField(max_length=250)
#property
def currency(self):
return self.debit_account.currency
This can however be inefficient if you have to do this for a lot of FiatTransactions.
In that case it might be better to remove the currency property, and annotate the QuerySet with:
from django.db.models import F
FiatTransaction.objects.annotate(currency=F('debit_account__currency'))
The FiatTransactions that arise from this will have an extra attribute named .currency that will contain the .currency of the .debit_account.
If you need this often, you can make use of a Manager that will automatically annotate when you access FiatTransaction.objects:
from django.db.models import F
class FiatTransactionManager(models.Manager):
def get_queryset(self, *args, **kwargs):
return super().get_queryset(*args, **kwargs).annotate(
currency=F('debit_account__currency')
)
class FiatTransaction(models.Model):
# …
objects = FiatTransactionManager()

How to render a foreign key field in Django

Desired outcome: When I render a Poller and its associated comments
I would like to also render the Vote a user selected for the Poller along with his comment (Note: A user can only comment if he voted on that poller).
Side note: A user can make one vote to a Poller and post one comment to a Poller. He can only comment if he voted beforehand.
# Models
class Poller(models.Model):
poller_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
created_on = models.DateTimeField(auto_now_add=True)
created_by = models.ForeignKey(Account, on_delete=models.CASCADE)
poller_text = models.CharField(max_length=333)
poller_choice_one = models.CharField(max_length=20)
poller_choice_two = models.CharField(max_length=20)
class Vote(models.Model):
poller = models.ForeignKey(Poller, on_delete=models.CASCADE, related_name='vote')
user = models.ForeignKey(Account, on_delete=models.CASCADE)
created_on = models.DateTimeField(auto_now_add=True)
poller_choice_one_vote = models.BooleanField(default=False)
poller_choice_two_vote = models.BooleanField(default=False)
def __str__(self):
return f'Vote by {self.user}'
class Comment(models.Model):
poller = models.ForeignKey(Poller, on_delete=models.CASCADE, related_name='PollerComment')
user = models.ForeignKey(Account, on_delete=models.CASCADE)
created_on = models.DateTimeField(auto_now_add=True)
comment = models.TextField(max_length=350)
flag_count = models.IntegerField(default=0)
upvote_count = models.IntegerField(default=0)
downvote_count = models.IntegerField(default=0)
# View
#require_GET
def render_single_poller(request, poller_id):
# Retrieve comments associated to the Poller
comments_qs = PollerComment.objects.filter(poller_id=poller_id)
context = {
'comments_qs': comments_qs,
}
return render(request, 'pollboard/single_poller.html', context)
I tried to do it via a template filter like so:
# pollboard_tags.py
#register.filter(name='get_vote')
def get_voted(self):
self.vote_made = 'Test'
print(self.vote.poller_choice_one_vote)
if self.vote.poller_choice_one_vote:
self.vote_made = 'One'
else:
self.vote_made = 'Two'
return self.vote_made
# template
<div class="commentors-poller-choice">{{ comment|get_vote }}</div>
throws
RelatedObjectDoesNotExist at /poller/68c725eb-277e-4b5b-a61b-b4a02bf5e854/
PollerComment has no vote.
I fear that I'm already overcomplicating things here. I hope there is a more straightforward solution to this like idk expanding the comments queryset by the required information for rendering?
If a user can vote on a poller only once, you can filter with:
#register.filter(name='get_vote')
def get_voted(self):
vote = Vote.objects.get(poller=self.poller, user=self.user)
return 'One' if vote.poller_choice_one_vote else 'Two'

Save comment.id to another object

I want to save in my database the comment id which has been commented. For that I have two models: Comentario and Pregunta. Look below:
models.py
class Comentario (models.Model):
titulo = models.CharField(max_length=50)
texto = models.CharField(max_length=200)
autor = models.ForeignKey (Perfil, null=True, blank=True, on_delete=models.CASCADE)
fecha_publicacion = models.DateTimeField(auto_now_add=True)
tag = models.ManyToManyField(Tags, blank=True)
def __str__(self):
return (self.titulo)
class Pregunta (models.Model):
descripcion = models.CharField(max_length=150)
autor = models.ForeignKey (Perfil, null=True, blank=True, on_delete=models.CASCADE)
fecha_pregunta = models.DateTimeField(auto_now_add=True)
comentario_preguntado = models.ForeignKey(Comentario, null=True, blank=True, related_name="pregunta_set")
def __str__(self):
return (self.descripcion)
When a comment is commented I want to save the 'comentario' id as 'comentario_preguntado' id. For that I have created the next view:
views.py
def ComentarioListar2 (request):
aa=Puesto.objects.filter(nombre_puesto=request.user.nom_puesto).values_list('etiquetas')
bb=Tags.objects.filter(id__in=aa)
objects=Comentario.objects.filter(tag__in=bb).exclude(autor__id=request.user.id)
form = preguntaform(request.POST or None)
if request.method == 'POST' and form.is_valid():
form.instance.autor = request.user
form.instance.comentario_preguntado=request.comentario.id
form.save()
return render(request, 'home/comentario_listar.html', {'objects': objects, 'form': form})
urls.py
urlpatterns = [
url(r'^listar2$', views.ComentarioListar2, name="listar2"),
]
But I obtain this error "ComentarioListar2() missing 1 required positional argument: 'Comentario_id'"
I do not know how to save in the comentario_preguntado id the id of the comment it is commented (comentario_id).
thank you for your help
Your URL needs to be declared so that the primary key of the model instance can be referred to from the view.
It should be like this:
url(r'^listar2/(?P<Comentario_id>[0-9]+)/$', views.ComentarioListar2, name="listar2"),
So, an example of the URL would be /listar2/101/. Where 101 is the ID of your Comentario model instance.
Then, you can access it in the view with the function you have defined:
def ComentarioListar2 (request, Comentario_id):
^^^^^^^

Using two django model forms on the same view

I have a PatientRegistrationForm and a PatientBillingForm form in a single view RegisterPatient.
When when I submit the patient form (form), the submitted date is stored in the database, nut the billing form (form1) only updates the staff and patient fields and nothing is stored in the payment_type, amount and receipt_number.
Please can anyone help point out why the second form is not being updated on the database?
Here is the views, models, forms and template code:
views.py
def RegisterPatient(request):
# bills = obj.bill_set.all()
form = PatientRegistrationForm(request.POST or None)
form1 = PatientBillingForm(request.POST or None)
if form.is_valid() and form1.is_valid():
instance = form.save(commit=False)
instance1 = form1.save(commit=False)
payment_type = form1.cleaned_data["payment_type"]
amount = form1.cleaned_data["amount"]
receipt_number = form1.cleaned_data["receipt_number"]
first_bill = Billing()
first_bill.payment_type = payment_type
first_bill.amount = amount
first_bill.receipt_number = receipt_number
# first_bill.saff
# first_bill.patients
print first_bill.payment_type, first_bill.amount, first_bill.receipt_number
first_name = form.cleaned_data["first_name"]
last_name = form.cleaned_data["last_name"]
other_name = form.cleaned_data["other_name"]
phone_number = form.cleaned_data["phone_number"]
new_patient = Patient()
new_patient.patient_number = UniquePatientNumber()
new_patient.first_name = first_name
new_patient.last_name = last_name
new_patient.other_name = other_name
new_patient.save()
first_bill.save()
model.py
class Patient(models.Model):
patient_number = models.CharField(max_length=120, unique = True, )
first_name = models.CharField(max_length = 120)
last_name = models.CharField(max_length=120)
other_name = models.CharField(max_length = 120, blank=True, null=True)
phone_number = models.CharField(max_length=15)
def _unicode_(self):
return self.patient_number
class Billing(models.Model):
staff = models.ForeignKey(MyUser, default=1)
patients = models.ForeignKey(Patient, default=1)
payment_type = models.CharField(max_length=100)
amount = models.DecimalField(max_digits=100, decimal_places=2, default=0.00)
receipt_number = models.CharField(max_length=120)
payment_date = models.DateTimeField(auto_now=False, auto_now_add=True)
def _unicode_(self):
return self.staff.username
def new_user_receiver(sender, instance, created, *args, **kwargs):
if created:
new_patient, is_created = Billing.objects.get_or_create(patients=instance)
post_save.connect(new_user_receiver, sender=Patient)
def new_user_creator(sender, instance, created, *args, **kwargs):
if created:
new_user, is_created = Billing.objects.get_or_create(staff=instance)
post_save.connect(new_user_creator, sender=MyUser)
form.py
class PatientRegistrationForm(forms.ModelForm):
class Meta:
model = Patient
exclude = ["patient_number"]
class PatientBillingForm(forms.ModelForm):
class Meta:
model = Billing
fields = ["payment_type","amount","receipt_number"]
forms.html
<form method="POST action="">{% csrf_token %}
{{ form }}
{{ form1 }}
<input type="submit" value="Submit"/>
</form>
In your view function you always create new Billing to MyUser with pk=1 and Patients with pk=1 because you set the default values in Billing fields default=1. You should remove default=1 and set null=True,blank=True instead.
If I understand your logic. You want to create new Billing information to every new User oder new Patient. I guess that in your view function you want to update or create Billing information to a patient. If so you should call
Billing.objects.filter(patients__pk=new_patient.pk).update(payment_type=payment_type,amount=amount,receipt_number=receipt_number)
after new_patient.save() then you could comment out lines with first_bill.
Update
1) comment out the line Billing.objects.filter(...)
2) comment out post_save.connect(new_user_receiver, sender=Patient) in models.py
3) activate the lines with first_bill again and add:
first_bill.patients=new_patient after new_patient.save()

How do I map one models objects to another models objects in a view

I have a bunch of message records that I would like to assign to different taskboxes.
#models.py
class TaskBox(models.Model):
name = models.CharField(max_length=64, blank=False)
def __str__(self):
return u'%s' % (self.name)
class Admin:
pass
class InboxEntry(models.Model):
job_number = models.CharField(max_length=14, unique=False, blank=False, null=False)
job_name = models.CharField(max_length=64, unique=False, blank=False, null=False)
request = models.CharField(max_length=64, choices=PRINT_CHOICES, blank=True, null=True)
date_due = models.DateTimeField(("Due"),auto_now=False)
note = models.TextField(max_length=1000, unique=False, blank=True, null=True)
assigned_by = models.ForeignKey(UserProfile, blank=False, null=False)
box = models.ForeignKey(TaskBox)
assigned_to = models.ManyToManyField(UserProfile, related_name='name', blank=True)
status = models.CharField(max_length=30, choices=STATUS_CHOICES, default="Awaiting Action")
def __str__(self):
return u'%s %s' % (self.job_number, self.job_name)
class Admin:
pass
class Meta:
ordering = ['status']
The idea is for the template to have some generic tags like {{ for taskbox in taskboxes }} to create a separate div for each taskbox that will hold a table for that box's records. My problem is constructing the view function...
#views.py
def display_prdInboxEntry(request, id):
if request.method == 'POST':
form = PrdInboxForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect('taskmanager/display/'+ id +'/')
else:
form = PrdInboxForm(request.POST)
return HttpResponseRedirect('taskmanager/display/'+ id +'/')
else:
form = PrdInboxForm()
user = request.user
**taskboxes = TaskBox.objects.all()
records_1 = InboxEntry.objects.filter(taskboxes[id]=1)
records_2 = InboxEntry.objects.filter(taskboxes[id]=2)
records_3 = InboxEntry.objects.filter(taskboxes[id]=3)
..... **
return render_to_response('taskmanager/taskmanager_view.html', {'form': form, 'taskboxes': taskboxes, 'records_1' : records_1, 'records_2' : records_2, 'records_3' : records_3, 'user': user}, context_instance=RequestContext(request))
The InboxEntry model has a field called "box" that's just a reference to the TaskBox model. I need a way to map say... TaskBox id 1 with all of the InboxEntry objects with "box = 1" so that I can populate the templates appropriately. Can I construct the function to accommodate this, or am I going about it the wrong way entirely?
It sounds like you're looking for the automatically-generated attribute for reverse lookups. You can get a QuerySet of all InboxEntries associated with a TaskBox like this:
TaskBox.objects.filter(id=1).inboxentry_set.all()
See the documentation on related objects.