Using "in" operator in Django template not working as expected - django

Could really use help figuring out what I'm misunderstanding about the following: I'm using the built-in "in" operator to check if a user (standard user model) is in a queryset of Board members.
This is the Board member model:
class BoardMembers(models.Model):
board = models.ForeignKey(Board, related_name="memberships", blank=True)
user = models.ForeignKey(User,related_name='user_boards', blank=True)
member_role = models.CharField(choices=MEMBER_ROLES, max_length=100, blank=True)
def __str__(self):
return self.user.username
Here is the view I'm using:
class ViewBoard(SelectRelatedMixin, generic.DetailView):
model = models.Board
select_related = ("user",)
template_name = 'board/view_board.html'
def get_context_data(self, **kwargs):
context = super(ViewBoard, self).get_context_data(**kwargs)
context['boardmembers_list'] = BoardMembers.objects.filter(board__slug=self.kwargs['slug'])
return context
And this is the html part I'm struggling with:
{% if user in boardmembers_list %}
<h1>HEY, {{ user.username }}, YOU'RE ALREADY A MEMBER!</h1>
{% else %}
<a class="btn btn-primary" href="{% url 'board:join_board' slug=board.slug pk=board.pk %}">Join this Board</a>
{% endif %}
I tested to see if individually the objects could be retrieved in the template and both the user and the boardmembers_list show up correctly.
From the docs it looks like this should be a relatively straightforward thing to do. Can anyone tell me where I'm going wrong or what I'm misunderstanding about how these operators work?

I think its better to decide if the user is in board or not in the View, instead of template.
class ViewBoard(SelectRelatedMixin, generic.DetailView):
...
def get_context_data(self, **kwargs):
board_mem_list = BoardMembers.objects.filter(board__slug=self.kwargs['slug'])
context = super(ViewBoard, self).get_context_data(**kwargs)
context['boardmembers_list'] = board_mem_list
context['user_in_board'] = board_mem_list.filter(user=self.request.user).exists() # this will check if user is already in boardmemberlist
return context
In template:
{% if user_in_board %}
<h1>HEY, {{ user.username }}, YOU'RE ALREADY A MEMBER!</h1>
{% else %}
<a class="btn btn-primary" href="{% url 'board:join_board' slug=board.slug pk=board.pk %}">Join this Board</a>
{% endif %}
And you are trying to search userin boardmemebers_list, and it won't work because user is a User model instance and boardmemebers_list is a queryset of BoardMember model where user is just a field of that model.

Related

How to perform addition (+) of attributes from multiple model instances connected via one ManytoManyField in Django?

Problem Statement: I want to add up the Activity.models attribute net_cost from within Trip.models i.e. connected via ManytoManyField. I have 3 or more choices per instance, so add: from the template language is inadequate (https://docs.djangoproject.com/en/4.1/ref/templates/builtins/#add)
More Context: Activity is connected to Trip via ManytoManyField. Accessing and adding up via save method in models.py is causing id needs to be assigned first error, I believe since ManytoMany can only be assigned once an instance of the model is saved.
Even if I access them all in the views.py before rendering, the context passed even after iterating over each object in list, can only repeat a common context["grand_total"] for all entries rendered in the template, whereas I need grandtotal for each Trip instance in frontend List.view.
Models:
Activity ->
class Activity(models.Model):
activity_location = [
...
]
acitivity_duration = [
...
]
activity_title = models.CharField(max_length=20, unique=True)
...
net_cost = models.PositiveIntegerField(validators=[MaxValueValidator(100000), MinValueValidator(0)], default=0)
class Meta:
ordering = ['-margin']
def __repr__(self):
return f"{self.activity_title} - {self.activity_location} - {self.net_cost}"
def __str__(self):
return f"Activity {self.activity_title}, Location {self.activity_location}, Duration {self.acitivity_duration}, Cost {self.net_cost}"
def get_absolute_url(self):
return reverse('activitys-list')
Trip ->
class Trip(models.Model):
transfer_choices = [
...
]
transfers = models.CharField(max_length=11, choices=transfer_choices, blank=True, null=True)
activity = models.ManyToManyField(Activity, related_name="activities", blank=True, verbose_name='Activities', help_text='select multiple, note location tags')
....
class Meta:
ordering = ['-entry_created']
def __repr__(self):
return f'{self.customer.name} for {self.duration} day/s'
def __str__(self):
return f"{self.customer.name} - {self.duration} - {self.start_date} - {self.end_date}"
def save(self, *args, **kwargs):
...
#successfully processing all inhenrent model attributes for grand total, but
unable to process activity.net_cost for all entries.
super(Trip, self).save(*args, **kwargs)
def get_absolute_url(self):
return reverse('trip-lists')
View:
class TripLists(LoginRequiredMixin, ListView):
login_url = '/login/'
redirect_field_name = 'index'
model = Trip
template_name = 'gobasic/trip_list.html'
paginate_by = 10
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super().get_context_data(**kwargs)
# Add in extra QuerySets here
Trips = Trip.objects.all()
activity_total = 0
for i in Trips:
for a in i.activity:
activity_total += a.net_cost
# Is pointless as same gets repeated.
context['activity_total'] = activity_total
context['total_trips'] = Trip.objects.all().count()
return context
Template:
<h2>Trip List - {{total_trips}}</h2>
<p><button type="button" class="btn btn-info">Add Trip</button>
<a href="{% url 'index' %}"> <button type="button" class="btn btn-info">Home</button>
</a></p>
<ol>
{% for t in object_list %}
<li><strong>🆔:</strong> {{ t.customer.name}}<strong> PB:</strong> {{ t.hotel_pb.hotel_name }} <strong>HV:</strong> {{ t.hotel_hv.hotel_name }} <strong>NL:</strong> {{ t.hotel_nl.hotel_name }} <!--<strong>Activities:</strong> {% for activity in t.activity.all %} {{ activity.activity_title}}, {% endfor %}--> <strong>Start:</strong>{{t.start_date.date}} <strong>End:</strong>{{t.end_date.date}} <strong>🏨:</strong>{{t.hotel_cost}} 🚕 {{t.transfer_cost}} | 🤿 {% for act in t.activity.all %} {{ act.net_cost}} {% endfor %}<strong> Grand Total= {{t.hotel_cost |add:t.transfer_cost}}</strong> <button class="btn">🖋️</button> </li>
<br>
{% empty %}
<p>No Entries Detected Yet !</p>
{% endfor %}
</ol>
How do I get a grand total i.e. t.hotel_cost + t.transfer_cost + t.activity.net_cost (for all objects within activity i.e. ManytoMany inside object_list )

Django many-to-many making too many calls

I have a simple m2m relationship as below:
class Category(ModelBase):
name = models.CharField(max_length=255)
icon = models.CharField(max_length=50)
class Course(ModelBase):
name = models.CharField(max_length=255, unique=True)
categories = models.ManyToManyField(Category, related_name="courses")
I am using ListView to show all the courses in a category or all courses if no category provided.
views.py
class CourseListView(ListView):
model = Course
paginate_by = 15
template_name = "courses.html"
context_object_name = "courses"
def get_queryset(self):
queryset = (
super()
.get_queryset()
.select_related("tutor")
.prefetch_related("categories")
.filter(active=True)
)
category_id = self.kwargs.get("category_id")
return (
queryset
if not category_id
else queryset.filter(categories__in=[category_id])
)
def get_context_data(self, *args, **kwargs: Any) -> Dict[str, Any]:
context = super().get_context_data(**kwargs)
category_id = self.kwargs.get("category_id")
if category_id:
context["current_category"] = Category.objects.get(id=category_id)
context["categories"] = Category.objects.all()
return context
Django is making duplicate calls as I am doing something like this in the template.
<div class="icon"><span class="{{ course.categories.first.icon }}"></span></div>
Not sure why, help much appreciated. Thanks!
When you do .prefetch_related('categories') the result of this prefetch will be used when you access course.categories.all. Any other queryset on course.categories will do a fresh query. Since course.categories.first is a new queryset, it does not use the prefetched result.
What you want to access in your template is the first result from course.categories.all(). But this is not easy in the template. I would recommend a method on the Course model:
class Course(...):
...
def first_category(self):
# Equivalent to self.categories.first(), but uses the prefetched categories
categories = self.categories.all()
if len(categories):
return categories[0]
else:
return None
And then in your template you can call this method
<div class="icon"><span class="{{ course.first_category.icon }}"></span></div>
You can also access the first value like:
{{ course.categories.all.0.icon }}
It is not necessary to write a method.
because categories is ManyToMany which means one category may appear in many courses, but in the template you just calling the first category's icon, so there maybe more than two course with the same first category, and it will retrieve them all, i recommend using another for loop to loops through categories too.
{% for course in courses %}
<div>
<h1>{{ course.name</h1>
......
<h4>categories</h4>
{% for category in course.categories %}
<div class="icon"><span class="{{ category.icon }}"></span></div>
{% endfor %}
</div>
{% endfor %}

How to update a specific manytomany field in django

My Area model has an exercise attribute with a ManyToManyField to my Exercise model:
class Area(models.Model):
name = models.CharField(max_length=100)
exercise = models.ManyToManyField(Exercise)
class Exercise(models.Model):
name = models.CharField(max_length=100)
My AreaView displays a list of areas, which each link to their own list of specific exercises, shown by AreaDetailView:
class AreaView(ListView):
model = Area
template_name = 'workouts/areas.html'
class AreaDetailView(DetailView):
model = Area
template_name = 'workouts/exercises.html'
def get_context_data(self, **kwargs):
context = super(AreaDetailView, self).get_context_data(**kwargs)
context['e_form'] = AddExerciseForm
return context
e.g:
areas.html
- abs
- biceps
- cardio
- legs ...
exercises.html
Abs
- Ab-wheel
- Cable-crunch
- Plank ...
Biceps
- Barbell curl
- Cable curl
- Dumbbell curl
AreaDetailView also displays a form which I would like to allow the user to create their own exercises, which will be specific to their corresponding area.
Here is my form:
class AddExerciseForm(forms.ModelForm):
class Meta:
model = Exercise
fields = ['name']
My template:
<form action="{% url 'exercise_add_new' %}" method="post">
{% csrf_token %}
{{ e_form }}
<button type="submit">Save changes</button>
</form>
My url:
path('exercise-add-new', ExerciseFormView.as_view(), name='exercise_add_new'),
And here is my CreateView which is supposed to handle the logic:
class ExerciseFormView(CreateView):
form_class = AddExerciseForm
success_url = '/'
def form_valid(self, form):
form.save()
new_ex = Exercise.objects.latest('id')
area = Area.objects.get(id=1)
area.exercise.add(new_ex)
return super(ExerciseFormView, self).form_valid(form)
This allows me to update the first object in my Area model ok, but I need to adjust the value of the variable area in form_valid so that the current 'id' is updated. For example if I click on 'Biceps' and then complete the form, I want to add an exercise related to 'id=2'
I have tried area = Area.objects.get(id=self.kwargs['id'])and other similar variations but so far nothing I have tried has worked
In ExerciseFormView are you trying to add a new exercise to an area or create a new area?
If adding a new exercise you will have to pass the area-id from the URL something like add_exercise/<area_id>, if doing the latter it should be straightforward.
You have pass area-id in URL you can do like below
path('exercise-add-new/<int:area_id>/', ExerciseFormView.as_view(),
name='exercise_add_new')
Then update your view as below
def form_valid(self, form):
form.save()
area = Area.objects.get(pk=self.kwargs["area_id"])
new_ex = Exercise.objects.latest('id')
area.exercise.add(new_ex)
return super(ExerciseFormView, self).form_valid(form)
Also update template as :
<form method="post">
{% csrf_token %}
{{ e_form }}
<button type="submit">Save changes</button>
</form>

How to overide django modelform to achieve custom behaviour

I have an Item object that has a manytomany relation to another object Option. I create a modelform from the Item object like so;
class Item(models.Model):
category = models.ForeignKey(Category)
name = models.CharField(max_length=200)
price = models.DecimalField(max_digits=9, decimal_places=2, blank=True, null=True)
options = models.ManyToManyField(Option)
class OptionForm(ModelForm):
options = forms.ChoiceField(widget=forms.RadioSelect())
class Meta:
model = Item
fields = ( 'options', )
When i render the form in the template it renders all available options for the Item object(the expected behavior) even those not created by a specific item. I want to be able to load options defined by the specific Item that will be chosen by the user. How do i override the form to achieve such behavior.for example without a form i can render an Items own Options through its id. item = Item.objects.get(pk=id)
Its tough to make ModelForm's defined on the fly because they are intimately tied to the structure in your model. Nevertheless you could use some clever template control flow and view rendering to get your desired effect. This is untested so you millage with this might vary.
<form method="post" action="">
{{ formset.management_form }}
{% for form in formset %}
{{ form.id }}
<ul>
{% if user_option.category %}
<li>{{ form.caregory }}</li>
{% endif %}
{% if user_option.name %}
<li>{{ form.name }}</li>
{% endif %}
{% if user_option.p_opt %}
<li>{{ form.price }}</li>
<li>{{ form.options }}</li>
{% endif %}
</ul>
{% endfor %}
</form>
From the Djano docs here.
Try overriding the form's init method and passing in the Item pk as an additional argument. The trick here is to pop the argument before calling the parent init.
class ItemOptionsForm(forms.ModelForm):
class Meta:
model = Item
def __init__(self, *args, **kwargs):
# pop 'item_id' as parent's init is not expecting it
item_id = kwargs.pop('item_id', None)
# now it's safe to call the parent init
super(ItemOptionsForm, self).__init__(*args, **kwargs)
# Limit options to only the item's options
if item_id:
try:
item = Item.objects.get(id=item_id)
except:
raise ValidationError('No item found!')
self.fields['options'] = forms.ChoiceField(item.options)
Then, in your view, create the form like:
form = ItemOptionsForm(item_id=item_id)
The nice thing about this is that you can raise ValidationErrors that will show up in the form.
Be aware that this doesn't prevent someone from POSTing option IDs to your form which do not belong to the Item, so you'll likely want to override the ModelForm.clean() to validate the options as well.
learning from link django: How to limit field choices in formset? provided by #jingo, i solved the problem by first of all creating dynamic form like so;
def partial_order_item_form(item):
"""dynamic form limiting optional_items to their items"""
class PartialOrderItemform(forms.Form):
quantity = forms.IntegerField(widget=forms.TextInput(attrs={'size':'2', 'class':'quantity','maxlength':'5'}))
option = forms.ModelChoiceField(queryset=OptionalItems.objects.filter(item=item),widget= forms.RadioSelect())
return PartialOrderItemform
then validating form like so;
def show_item(request,id):
option = get_object_or_404(Item,pk=id)
if request.method == 'POST':
form = partial_order_item_form(option)
#bound form to POST data,
final_form = form(request.POST)
# check validation of posted data
if final_form.is_valid():
order.add_to_order(request)
url =urlresolvers.reverse('order_index',kwargs={'id':a.id})
# redirect to order page
return HttpResponseRedirect(url)
else:
form = partial_order_item_form(item=id)
context={
'form':form,
}
return render_to_response('item.html',context,context_instance=RequestContext(request))

CharField values disappearing after save (readonly field)

I'm implementing simple "grade book" application where the teacher would be able to update the grades w/o being allowed to change the students' names (at least not on the update grade page). To do this I'm using one of the read-only tricks, the simplest one. The problem is that after the SUBMIT the view is re-displayed with 'blank' values for the students. I'd like the students' names to re-appear.
Below is the simplest example that exhibits this problem. (This is poor DB design, I know, I've extracted just the relevant parts of the code to showcase the problem. In the real example, student is in its own table but the problem still exists there.)
models.py
class Grade1(models.Model):
student = models.CharField(max_length=50, unique=True)
finalGrade = models.CharField(max_length=3)
class Grade1OForm(ModelForm):
student = forms.CharField(max_length=50, required=False)
def __init__(self, *args, **kwargs):
super(Grade1OForm,self).__init__(*args, **kwargs)
instance = getattr(self, 'instance', None)
if instance and instance.id:
self.fields['student'].widget.attrs['readonly'] = True
self.fields['student'].widget.attrs['disabled'] = 'disabled'
def clean_student(self):
instance = getattr(self,'instance',None)
if instance:
return instance.student
else:
return self.cleaned_data.get('student',None)
class Meta:
model=Grade1
views.py
from django.forms.models import modelformset_factory
def modifyAllGrades1(request):
gradeFormSetFactory = modelformset_factory(Grade1, form=Grade1OForm, extra=0)
studentQueryset = Grade1.objects.all()
if request.method=='POST':
myGradeFormSet = gradeFormSetFactory(request.POST, queryset=studentQueryset)
if myGradeFormSet.is_valid():
myGradeFormSet.save()
info = "successfully modified"
else:
myGradeFormSet = gradeFormSetFactory(queryset=studentQueryset)
return render_to_response('grades/modifyAllGrades.html',locals())
template
<p>{{ info }}</p>
<form method="POST" action="">
<table>
{{ myGradeFormSet.management_form }}
{% for myform in myGradeFormSet.forms %}
{# myform.as_table #}
<tr>
{% for field in myform %}
<td> {{ field }} {{ field.errors }} </td>
{% endfor %}
</tr>
{% endfor %}
</table>
<input type="submit" value="Submit">
</form>
Your way of displaying the readonly field is the problem.
Since the student field is disabled, the form submit will not have that as the input, so the error form that is displayed with validation error messages don't get the initial value.
That is why ReadOnly Widget has to be more complex than just being a html disabled field.
Try using a real ReadOnlyWidget, one that overrides _has_changed.
Following is what I use. For instantiation, it takes the original_value and optionally display_value, if it is different.
class ReadOnlyWidget(forms.Widget):
def __init__(self, original_value, display_value=None):
self.original_value = original_value
if display_value:
self.display_value = display_value
super(ReadOnlyWidget, self).__init__()
def _has_changed(self, initial, data):
return False
def render(self, name, value, attrs=None):
if self.display_value is not None:
return unicode(self.display_value)
return unicode(self.original_value)
def value_from_datadict(self, data, files, name):
return self.original_value
I'm stretching myself a little here, so some thoughts:
% Have you sniffed the traffic to see exactly what's being sent between browser and server?
% Do you need to send the student name as a hidden field (your db update thing may assume you want student blank if you don't)?
% Have you looked at the source of your HTML after Python parses it?