Retrieving data from junction table via foreign key - Laravel - mysql

I'm trying to get data from a junction table and display it, so I have a users table and my junction table prescription_user. Prescription_user table has a user_id and prescription_id.
So what I'm trying to do is on this userdetails page, show all of this users prescriptions and access the prescription name and description etc, however when I try any of this I get "Trying to get property of non-object"
The models are set up to eager load correctly I think, the user model has a hasMany relation with the prescription_user model, and the prescription_user model has a belongsTo the user model.
Controller
function viewUserDetails($userId)
{
$logged_user_id = Auth::user()->id;
$user = User::find($logged_user_id);
$prescriptions = Auth::user()->prescription_user;
$fullname = $user->firstname ." ". $user->surname;
$email = $user->email;
$address = $user->address;
$postcode = $user->postcode;
$dob = $user->dateofbirth;
$uniquepatientnumber = $user->uniquepatientnumber;
$data = [];
$data['fullname'] = $fullname;
$data['email'] = $email;
$data['address'] = $address;
$data['postcode'] = $postcode;
$data['dateofbirth'] = $dob;
$data['uniquenumber'] = $uniquepatientnumber;
$data['prescriptions'] = $prescriptions;
return view('user/userdetails')->withData($data);`
userdetails blade
#extends('layouts.master')
#section('title', 'My Details')
#section('content')
<h1>My Details </h1>
<ul>
<li>Name: {{ $data['fullname'] }}</li>
<li>Email: {{ $data['email'] }}</li>
<li>Address: {{ $data['address'] }}</li>
<li>Postcode: {{ $data['postcode'] }}</li>
<li>Date of Birth: {{ $data['dateofbirth'] }}</li>
<li>Your Unique number is: {{ $data['uniquenumber'] }}</li>
<li>Your are currently prescribed {{$data['prescriptions']}}</li>
<p>Note: If you believe any of these are incorrect please contact a receptionist</p>
</ul>
#endsection``

There are some design flaws in what you have explained. You have a User model and a Prescription model. That means prescription_user is your pivot table (not junction table). If so far I'm correct, it means User and Prescription have a Many to many relationship. To prove my point, you said
Prescription_user table has a user_id and prescription_id.
That means prescription_user is the pivot table.
In your user model, define a many to many relationship with prescription. And vice versa.
User model
public function prescriptions() {
return $this->belongsToMany(Prescription::class, 'prescription_user', 'user_id', 'prescription_id');
}
Then you can change your controller function like this
public function viewUserDetails()
{
$user = Auth::user();
$prescriptions = $user->prescriptions;
return view('user/userdetails', compact('user', 'prescriptions'));
}
And your view
#extends('layouts.master')
#section('title', 'My Details')
#section('content')
<h1>My Details </h1>
<ul>
<li>Name: {{ $user->firstname }} {{ $user->surname }}</li>
<li>Email: {{ $user->email }}</li>
<li>Address: {{ $user->address }}</li>
<li>Postcode: {{ $user->postcode }}</li>
<li>Date of Birth: {{ $user->dateofbirth }}</li>
<li>Your Unique number is: {{ $user->uniquepatientnumber }}</li>
</ul>
Your are currently prescribed
<ul>
#foreach($prescriptions as $prescription)
<li>{{ $prescription->name }}
#endforeach
</ul>
<p>Note: If you believe any of these are incorrect please contact a receptionist</p>
#endsection
Your code would be neater and working great
Update
Getting data from the pivot table is relatively simple. Define those columns in the relationship
public function prescriptions() {
return $this->belongsToMany(Prescription::class, 'prescription_user', 'user_id', 'prescription_id')->withPivot('column1', 'column2');
}
In your view, you display it like this
{{ $prescription->pivot->column1 }} {{ $prescription->pivot->column2 }}

First I'm not sure that withData() is a thing. Just ->with($data).
Second, there is some confusion at the beginning of your function. You pass in a $userId, that you never use. Then you get the id from Auth::user(), then get a user with that id, which you already had. So just $user = Auth::user();
Third, you only get that error trying to use the -> operator on a non-object, and you only use it on $user-> and Auth::user()->
Since you got the user from Auth::user in the first place, I'm guessing it failed right out of the box at Auth::user()->id, and there is no Auth::user, but you would have to post a little more data, either from the error in the browser or from storage/logs/laravel.log.
BUT, if I use this code
Route::get('/', function () {
$user = \Auth::user();
die($user->name);
return view('welcome');
}
where there clearly is no Auth::user() (I haven't even got to a login yet) I get this output:
ErrorException in web.php line 16:
Trying to get property of non-object
1. in web.php line 16
2. at HandleExceptions-> ... array('user'=>null)
So it's probaby that.

Related

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.

Display time stored in database in HTML (django)

I'm building a bus booking website using Django. Users fill From, To, Bus type(ordinary or Volvo) and date. Results display the buses available on that route on that particular date. I made three tables - Bus, Route, and Frequency. You can check my models.py and views.py here - https://dpaste.de/Oi6a I convert the date to the corresponding day of the week. I have filtered out the buses, However, I want to display time on the template. This is part of my views.py code:
def select(request):
bus_type = request.GET.get('bus_type')
bus_from = request.GET.get('bus_from')
bus_to = request.GET.get('bus_to')
date_string = request.GET.get('date')
date = datetime.strptime(date_string, '%Y-%m-%d')
day = calendar.day_name[date.weekday()]
kwargs = { '{0}__range'.format(day): ["00:00:00", "23:59:59"],}
qs = Frequency.objects.filter(bus__type_of_bus=bus_type, bus__route__location_from=bus_from, bus__route__location_to=bus_to, **kwargs)
context = {'qs': qs, 'date':date_string,}
template = 'select.html'
return render(request, template, context)
As you can see qs filters the buses available and is then passed to the template. This is a part of the template:
<div id="pricing-table" class="clear">
{% for info in qs %}
<div class="price_block">
<h3>{{ info.bus.type_of_bus }}<span>{% if info.bus.type_of_bus == 'Volvo' %} Rs 550 {% else %}Rs 330 {% endif %}</span></h3>
Book
<ul>
<li><b>Bus Number -</b> {{ info.bus.bus_number }}</li>
<li><b>Route -</b> {{ info.bus.route }}</li>
<li><b>Date -</b> {{ date }}</li>
<li><b>Time -</b> {{ info.day }}</li>
</ul>
</div>
In the last line of this HTML file, I have added info.day. What I want is that this displays the time that bus operates on the given day. For example, If a person searches a bus on 29th April. 29th April will be converted to corresponding day i.e. Friday. The frequency table has 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' and 'Sunday' as attributes and these all are TimeField(). qs filters the buses and passes to the template. I want {{ info.day }} to show the time when the bus operates. Instead, it shows nothing. But when I change {{ info.day }} to {{ info.Friday}} it start showing the time. How can I display the time without having to manually go and change the day in {{ info.(here) }} every time I search for a bus. I didn't make it very clear, but I hope you understand. Let me know if you don't.
You can create a custom template tag do this for you and a method on the model.
I haven't tested this code, but this should work with or without small adjustments
Add this method to the Frequency model:
def get_day_info(self, date):
# Where date_string would be a datestring
date = datetime.strptime(date_string, '%Y-%m-%d')
day = calendar.day_name[date.weekday()]
info_time = getattr(self, '{}'.format(day))
return info_time
And register this as a template tag.
#register.simple_tag
def get_day_info(info, date):
return info.get_day_info(date)
You would call it in the template like this:
<li><b>Time -</b> {% get_day_info info {{ date }} %}</li>

Laravel 5.2 get user's notes and note's users

I have two tables:
the first
Is users table which contain user information including user id.
The Second
Is notes table which container user notes including note id.
I have relation many to many between them and I have my intermediate table between them that contain the user_id which matches note_Id.
Everything works perfectly but I want to get user notes and other users that have the same note.
For example
NOTE 1 users with id 1, 2, 3 can see it.
I want to use a query that get all the notes that the logged in user created and also other users that can view this note.
what I have tried
$usernotes = User::where('id', '=', Auth::user() -> id) -> first();
#foreach($usernotes -> notes as $usernote)
{{ $usernote -> title }}
#endforeach
This return all the user notes but doesn't return the all note's users.
This query will get users (except an authenticated one as you ask) that have the same notes a currently authenticated user has:
Note::whereHas('users', function($q) {
$q->where('id', auth()->user()->id);
})->with(['users' => function($q) {
$q->where('id', '<>', auth()->user()->id);
}])->get();
$loggedInUser = Auth::user();
#foreach($user->notes as $userNote)
Note Title: {{ $userNote -> title }}
#foreach($userNote->users as $user)
User Name: {{ $user->name }}
#endforeach
#endforeach
To exclude loggedInUser:
$loggedInUser = Auth::user();
#foreach($user->notes as $userNote)
Note Title: {{ $userNote -> title }}
#foreach($userNote->users()->where('id', '!=', $loggedInUser->id)>get() as $user)
User Name: {{ $user->name }}
#endforeach
#endforeach

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

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 }}

Flask-SQLAlchemy queries

I am having issues with a seemingly simple sqlalchemy query using Flask.
I have a table called Links and within that table there are columns called 'id', 'author_id', 'link', and 'group'. My models.py looks like this:
class Links(db.Model):
__tablename__='links'
id = db.Column(db.Integer, primary_key=True)
author_id = db.Column(db.Integer, db.ForeignKey('users.id'))
link = db.Column(db.String, unique=False, nullable=True)
group = db.Column(db.String, unique=False, nullable=False)
def __init__(self, author_id=None, link=None, group=None):
self.author_id = author_id
self.link = link
self.group = group
def __repr__(self):
return'<Link %r>' %(self.link)
I would like to return the values of all groups associated with the user that is logged into the application. Here is my views.py file:
#app.route('/members/', methods=['GET','POST'])
#login_required
def members():
error=None
form = PostLink(request.form, csrf_enabled = False)
uid = session['user_id']
link = "NULL"
groups = Links.query.filter_by(author_id=uid).all()
if request.method=='POST':
if form.validate_on_submit():
new_group = Links(
uid,
form.group.data,
link,
)
try:
db.session.add(new_group)
db.session.commit()
flash("You have created a group!")
except IntegrityError:
error = 'That group did not work, maybe it already exists?'
else:
flash_errors(form)
return render_template('members.html', form=form, error=error, link = link, groups=groups)
And my 'members.html':
{% extends "base.html" %}
{% block content %}
<p>Add New Group: {{ form.group }}</p>
<input id="link" type="hidden" name="link" value= {{ link }}/>
<p><input type="submit" value="Request"></p>
</form>
<br/>
{% for group in groups %}
<li><p>
{{ group }}
</p></li>
{% endfor %}
{% endblock %}
Currently this is just returning a list of links and groups in an odd format:
<Link u'link2'>
<Link u'linky'>
<Link u'linkymaybe'>
<Link u'madeit'>
<Link u'BLAH'>
So the core of my question is how do I build a query using SQLAlchemy to display all groups associated with the logged in user (uid = session['user_id']) I am pretty new to Flask and this problem is becoming an issue as I have tried a number of filter_by and filter statements with no luck at all.
Thank you in advance!
It is displaying correctly the object "Link" returned by the query.
You need to format it in the template.
Thisi is link {{ group.link }} from author #{{ group.author_id }} in group named {{ group.group }}
Maybe you've chosen a bad name "group" when cycling on results in the template. It should be called link.
In the template, you can show the group name using {{ link.group }} instead of {{ link }}. The query is returning the whole object.