Scenario: I have some boxes (containers) I have some objects (samples) a sample can be split over many boxes, a box can contain many samples.
I want to be able to assign a sample to a box and remove a sample from a box.
I followed these tutorials 57-59, assigning friends to users, and got it working.
So I now try to adapt the code, so I need to change users to boxes/containers and friends to samples. Sounds simple enough. But I'm inexperienced with the quirks of Django and where the request.user is I can't seem to get the correct syntax. So here comes the code, first the code working from the tutorial, then my attempt at refactoring it.
I have 2 other tables/models Containers and Sample which the ContainerContent model fits inbetween.
# models.py (tutorial)
class Friend(models.Model):
users = models.ManyToManyField(User)
current_user = models.ForeignKey(User, related_name='owner', null=True, on_delete = models.PROTECT)
# container_id = models.ForeignKey(Container, null=True, on_delete = models.PROTECT)
#classmethod
def make_friend(cls, current_user, new_friend):
friend, created = cls.objects.get_or_create(
current_user=current_user
)
friend.users.add(new_friend)
#classmethod
def lose_friend(cls, current_user, new_friend):
friend, created = cls.objects.get_or_create(
current_user=current_user
)
friend.users.remove(new_friend)
# views.py
def change_friends(request, operation, pk):
friend = User.objects.get(pk=pk)
if operation == 'add':
Friend.make_friend(request.user, friend)
elif operation == 'remove':
Friend.lose_friend(request.user, friend)
return redirect('depot:allcontainer')
#urls.py
url(r'^container/(?P<operation>.*)/(?P<pk>\d+)/$', views.change_friends, name='change_friends'),
#html
...
<tbody>
{% for user in users %}
<tr>
{% if user not in friends %}
<!-- we will want to add an if stmt list if not in unassigned - need to think how to do this -->
<td>{{ container.container_id }}</td>
<td>{{ user.username }}</td>
<td> <a href="{% url 'depot:change_friends' operation='add' pk=user.pk %}" class="badge badge-primary" role="button">
<!-- container=container.container_id -->
<!-- container=container.container_id -->
<!-- <button type="button" class="btn btn-success">add</button> -->
>>
</a></td>
{% endif %}
</tr>
{% endfor %}
</tbody>
...
...
<tbody>
<tr>
{% for friend in friends %}
<td><a href="{% url 'depot:change_friends' operation='remove' pk=friend.pk %}" class="badge badge-primary" role="button">
<<
</a></td>
<td>{{ friend.username }}</td>
</tr>
{% endfor %}
</tbody>
...
Below is my Attempt:
# models.py
class ContainerContents(models.Model):
sample = models.ManyToManyField('Sample')
current_container = models.ForeignKey(Container, null=True, on_delete = models.PROTECT)
#classmethod
def add_to_container(cls, current_container, new_sample):
sample, created = cls.objects.get_or_create(
current_container=current_container
)
sample.add(new_sample)
#classmethod
def remove_from_container(cls, current_container, new_sample):
sample, created = cls.objects.get_or_create(
current_container=current_container
)
sample.remove(new_sample)
# views.py - this one is causing me issues, the request.____
def change_container(request, operation, pk, fk='', sample_id=''):
container = Container.objects.get(pk=pk)
sample = Sample.objects.get(pk=fk)
# sample = Container.objects.get(container.sample_id=sample_id)
if operation == 'add':
ContainerContents.add_to_container(request.container, container)
elif operation == 'remove':
ContainerContents.remove_from_container(request.container, container)
return redirect('depot:allcontainer')
# urls.py
url(r'^change_container/(?P<operation>.*)/(?P<pk>\d+)/sample/(?P<fk>\d+)$', views.change_container, name='change_container'),
I suspect I need to pass the container id here otherwise there will not be any distinction between the containers.
# html
<tbody>
{% for unassigned in container_contents %}
<tr>
<!-- { if user not in friends } -->
<!-- we will want to add an if stmt list if not in unassigned - need to think how to do this -->
<td>{{ unassigned.area_easting }}.
{{ unassigned.area_northing }}.
{{ unassigned.context_number }}.
{{ unassigned.sample_number }}</td>
<td>{{ unassigned.sample_id }}</td>
<td></td>
<td> <a href="{ url 'depot:change_friends' operation='add' pk=user.pk }" class="badge badge-primary" role="button">
<!-- container=container.container_id -->
<!-- container=container.container_id -->
<!-- <button type="button" class="btn btn-success">add</button> -->
>>
</a></td>
<!-- { endif } -->
</tr>
{% endfor %}
</tbody>
...
...
<tbody>
<tr>
{% for contents in container_contents %}
<td><a href="{% url 'depot:change_container' operation='remove' pk=container.container_id fk=contents.sample_id %}" class="badge badge-primary" role="button">
<!-- <button type="button" class="btn btn-default">remove</button> -->
<<
</a></td>
<td>{{ contents.sample_id }}</td>
<td>{{ contents.area_easting }}.
{{ contents.area_northing }}.
{{ contents.context_number }}.
{{ contents.sample_number }}</td>
</tr>
{% endfor %}
</tbody>
...
--- Update ---
I should have included the view that generates the page, not the users/friends is still contained in it and will be removed once I get it working.
def detailcontainer(request, container_id):
container = get_object_or_404(Container, pk=container_id)
samples = container.samples.all()
# allsamples = container.samples.exclude(sample_id=samples)
allsamples = container.samples.all()
users = User.objects.exclude(id=request.user.id).order_by('-id')
friend = Friend.objects.get(current_user=request.user)
friends = friend.users.all().order_by('-id')
container_contents = container.samples.all()
# container_contents = Container.objects.get(current_container=samples)
return render(request, 'container/detailcontainer.html',
{'container':container,
'samples':samples,
'allsamples': allsamples,
'users': users,
'friends': friends,
'container_contents': container_contents,
})
It should cause issues because request does not have any attribute named container. In your tutorial example, it got the logged in user using request.user, because django assigns logged in user instance to the request(through middleware).
As you already have sample and container objects in your change_container view method, you can try like this:
if operation == 'add':
ContainerContents.add_to_container(container, sample)
elif operation == 'remove':
ContainerContents.remove_from_container(container, sample)
Update
Missed one thing, you need to change inside add_to_container and remove_from_container method as well:
#classmethod
def add_to_container(cls, current_container, new_sample):
container, created = cls.objects.get_or_create(
current_container=current_container
)
container.sample.add(new_sample)
#classmethod
def remove_from_container(cls, current_container, new_sample):
container, created = cls.objects.get_or_create(
current_container=current_container
)
container.sample.remove(new_sample)
Because sample is a ManyToMany Field making connection between CurrentContainer and Sample model.
Update 2
#classmethod
def remove_from_container(cls, current_container, new_sample):
from app_name.models import ContainerSample
c_sample = ContainerSample.objects.get(container=current_container, sample=new_sample)
c_sample.delete()
You are not referencing your m2m field in fetched object. You'll need to address sample field as following:
models.py:
#classmethod
def add_to_container(cls, current_container, new_sample):
containerContents, created = cls.objects.get_or_create(
current_container=current_container
)
containerContents.sample.add(new_sample)
#classmethod
def remove_from_container(cls, current_container, new_sample):
containerContents, created = cls.objects.get_or_create(
current_container=current_container
)
containerContents.sample.remove(new_sample)
and set proper variables to your model methods:
views.py
def change_container(request, operation, pk, fk='', sample_id=''):
container = Container.objects.get(pk=pk)
sample = Sample.objects.get(pk=fk)
# sample = Container.objects.get(container.sample_id=sample_id)
if operation == 'add':
ContainerContents.add_to_container(container, sample)
elif operation == 'remove':
ContainerContents.remove_from_container(container, sample)
return redirect('depot:allcontainer')
Related
I am creating an app for a school schedule using Django. Admin users can currently add classes to a list. Student users can view that list but I want to create a button/link that will add that class object to a separate list that will act as their applied classes.
Models.py
class Class(models.Model):
def __str__(self):
return self.name
name = models.CharField(max_length=50)
crn = models.CharField(max_length=5, validators=[RegexValidator(r'^\d{1,10}$')])
grade_mode = models.CharField(max_length=50)
subject = models.CharField(max_length=3)
course_num = models.CharField(max_length=4, validators=[RegexValidator(r'^\d{1,10}$')])
section_num = models.CharField(max_length=3, validators=[RegexValidator(r'^\d{1,10}$')])
credit_hours = models.FloatField(validators=[MinValueValidator(0.0), MaxValueValidator(5.000)])
teacher = models.CharField(max_length=50)
Views.py
#login_required
#dec.student_required
def index(request):
class_list = Class.objects.all().order_by("-name")
# Allow for Posting of New Classes to Schedule.
if request.method == "POST":
form = ClassesForm(request.POST)
if form.is_valid():
form.save()
form = ClassesForm()
messages.success(request, "Class Added to Registry!")
else:
form = ClassesForm()
context_dict = {'classes': class_list,
'form': form}
return render(request, 'index.html', context=context_dict)
Index.HTML Add Button
<table>
<tbody>
{% if classes %}
{% for class in classes %}
<tr>
<td>Class Name: </td>
<td>Subject: </td>
<td>CRN: </td>
<td>Course Number: </td>
<td>Section Number: </td>
<td>Credit Hours: </td>
<td>Teacher: </td>
<td>Grade Mode: </td>
<td>Add Class</td>
</tr>
<tr>
<td>{{ class.name }}</td>
<td>{{ class.subject }}</td>
<td>{{ class.crn }}</td>
<td>{{ class.course_num }}</td>
<td>{{ class.section_num }}</td>
<td>{{ class.credit_hours }}</td>
<td>{{ class.teacher }}</td>
<td>{{ class.grade_mode }}</td>
<td>
<form method="GET">
{% csrf_token %}
<button class="addToSchedule" type="submit" value="add" name="Add Class">
Add Class
</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
{% else %}
<strong>There are no classes added.</strong>
{% endif %}
</table>
Image on Website.
So far I can print out the models on their own separate list as shown above but I don't know where to go in regards to the button or if a button is even the correct way of doing it. I want to click on a button and that model will be added and saved to the Added Classes section above.
hoping for some guidance around my below problem with displaying reverse lookup field in a template inside formsets.
Maintenance Item Model
class Maintenance_Item(CommonInfo):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=100, unique=True)
name_description = models.CharField(max_length=255)
is_active = models.BooleanField(default=True)
def __str__(self):
return self.name
Checklist Model
class MaintenanceCheckList(CommonInfo):
CHOICES = (
('P','Compliant'),
('F','Non-Compliant'),
)
id = models.AutoField(primary_key=True)
item = models.ForeignKey(Maintenance_Item, on_delete=PROTECT, related_name='item_name')
is_compliant = models.CharField(max_length=20, choices= CHOICES, default=CHOICES[0][0])
def __int__(self):
return self.item
EDIT Form
class MaintenanceCheckListComplianceForm(forms.ModelForm):
is_compliant = forms.ChoiceField(
choices=MaintenanceCheckList.CHOICES,
widget=forms.RadioSelect,
required=False,
)
class Meta:
model = MaintenanceCheckList
fields = ('item','is_compliant',)
END EDIT
The current template
<form class="" method='post'>
{% csrf_token %}
{{ form.management_form }}
<table class="table my-0" id="dataTable">
<thead>
<tr>
<th>Maintenance Items</th>
</tr>
</thead>
<tbody>
{% for sub_form in form %}
<tr>
{% for i in sub_form.item_name.all %}
<td>Item: {{ i.name }}</p>
{% endfor %}
{{ sub_form.item|add_class:"form-select" }}<p>
</p>{{ sub_form.is_compliant }}</td>
</tr>
{% endfor %}
</table>
<div class="divider"></div>
<div class="col-md-12">
<p class='pt-sm-2'>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
Currently, I have generic formset view which creates an row for each item in the Maintenance_Item which works brilliant and generates the formset view as shown.
The challenge, I have is I want to hide the ModelChoice fields (I can do that easily with a Widget), and just display the friendly name in the ModelChoice field as a simple Text Label in the template.
Note setting disabled won't work because POST ignores disabled fields (already troubleshooted using that)
I have seen many solutions for this on here but I can't seem to get any working as I am also new to django. Essentially as of now my files are uploading correctly by a user to media/documents but when I try to download the files from the directory, in terminal I get 404 and I end up downloading an empty file. Say the original file is "test.txt", right now it is downloading an empty "documents_test.txt". As of right now this is what I have for my code and how I am trying to download within my template.
models.py
class Program(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
description = models.CharField(max_length=128)
upload = models.FileField(upload_to='documents/')
category = models.ForeignKey(ProgramCategory, on_delete=models.CASCADE)
is_public = models.BooleanField()
views.py
def programs(request):
# Create some default categories if there aren't any.
if (not ProgramCategory.objects.all()):
ProgramCategory.objects.create(category="Hobby")
ProgramCategory.objects.create(category="School")
ProgramCategory.objects.create(category="Work")
'''if (request.method == "GET" and "toggle_completed" in request.GET):
id = request.GET["toggle_completed"]
task = Task.objects.get(id=id)
task.is_completed = not task.is_completed
task.save()'''
if (request.method == "GET" and "delete" in request.GET):
id = request.GET["delete"]
Program.objects.all().filter(id=id).delete()
return redirect("/programs/")
if (request.method == "POST"):
try:
user_profile = UserProfile.objects.filter(user=request.user).get()
except:
user_profile = UserProfile()
user_profile.user = request.user
#user_profile.tasks_view_hide_completed = False
#form = HideCompletedTasksForm(request.POST, instance=user_profile)
if (form.is_valid()):
form.save()
user_profile = UserProfile.objects.filter(user=request.user).get()
#hide_completed_form_data = HideCompletedTasksForm(instance=user_profile)
#except:
#hide_completed_form_data = HideCompletedTasksForm()
#hide_completed = hide_completed_form_data["tasks_view_hide_completed"].value()
#if (hide_completed):
#table_data = Task.objects.select_related().filter(user=request.user, is_completed=False)
else:
table_data = Program.objects.all().filter(user=request.user)
#filename = Program.objects.all().filter(user=request.user).values_list('upload')
context = {
#"hide_completed_form_data": hide_completed_form_data,
"table_data": table_data,
}
template(programs.html)
{% for row in table_data %}
<tr>
<td>{{ row.upload }}</td>
<td>{{ row.description }}</td>
<td>{{ row.category }}</td>
.
.
.
<td>
<a class="btn btn-primary" href="/programs/edit/{{ row.id }}/">Edit</a>
<a class="btn btn-primary" href="#" onclick="confirmDeleteModal({{ row.id }})">Delete</a>
<a class='btn btn-primary' href="{{ row.upload.url}}" download="{{ row.upload.url}}"> Download</a>
</tr>
{% endfor %}
{% endif %}
Any help would be greatly appreciated.
How to display only some columns of Django model in a HTML template?
And also: how do I perform a function on one of the records? (amount)?
Right now I'm displaying a whole table of model like that:
my models.py
class Tabela(models.Model):
block_id = models.CharField(max_length=64)
timestamp = models.DateTimeField()
type = models.CharField(max_length=32)
link = models.CharField(max_length=64)
link_as_account = models.CharField(max_length=100)
account = models.CharField(max_length=100)
amount = models.CharField(max_length=64)
def __str__(self):
return self.block_id
My views.py
def search_results(request):
model = Tabela
query_addresse = request.GET.get('addressee', None)
query_hash = request.GET.get('hash', None)
if not query_hash and not query_addresse and request.method == 'GET':
return render(request, 'nanosite/index.html', {})
if query_hash and request.method == 'GET':
if query_addresse:
result = Tabela.objects.filter(account=query_addresse, block_id=query_hash)
else:
result = Tabela.objects.filter(block_id=query_hash)
field_names = [f.name for f in model._meta.get_fields()]
data = [[getattr(ins, name) for name in field_names]
for ins in result]
elif query_addresse and request.method == 'GET':
result = Tabela.objects.filter(account=query_addresse)
field_names = [f.name for f in model._meta.get_fields()]
data = [[getattr(ins, name) for name in field_names]
for ins in result]
return render(request, 'nanosite/index.html', {'field_names': field_names, 'data': data})
My index.html
<div id="bottomhalf" class="table-responsive">
<table class="table table-sm table-dark table-hover">
<thead class="thead-light">
{% for head in field_names %}
<th scope="col">{{ head }}</th>
{% endfor %}
</thead>
<tbody>
{% for row in data %}
<tr scope="row">
{% for cell in row %}
<td>{{ cell }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
What I'd like to do is display only block_id, timestamp, account and amount in html. I've tried different approaches like using only the result part of views without field_names and data, but of course it didn't work.
My other question is, how can I modify the field amount and perform an operation on it to be displayed in template like amound divided by certain digit with a $ sign before it (for example if amount=1488 to be divided by 124 and displayed as '$12')?
Pass the queryset qs selecting the objects to display to the template and iterate over it to generate your table:
{% for obj in qs %}
<tr>
<td> {{obj.block_id}} </td>
<!-- etc ... -->
</tr>
{% endfor %}
Now, if you also want to pass a variable specifying the names of the fields of the object to tabulate, and in what order, you find out that the Django template engine is by design (!) incapable of doing that. You can either do what you are doing, and generate a list-of-rows in Python which you pass to the Template, or you need a Django custom template tag such as
#register.filter
def attr( obj, name):
return getattr( obj, name, '')
and then you can run an inner loop in your template
<tr>
{% for name in selected_field_names %}
<td> {{obj|attr:name}} </td>
{% endfor %}
</tr>
The answer to the second question, is to define a property on your model to return the field suitably transmogrified:
class Tabela(models.Model):
...
#property
def funny_amount(self):
val = self.amount/12.0
return f'$ {val:.2f}'
and refer to {{obj.funny_amount}} in your template
I am trying to show a detailed view of the contacts stored in a phonebook. The PhoneBook(id, name) is one of my models which is a foreign key to model Contact(id, first_name, last_name, phone_number, phone_book).
In my index page, there is a button which opens the phone book. After that, I want it such that the user may click on a phone book and the detailed view(first_name, last_name, phone_number) would be shown to them.
In view.py, I have a function which captures all the phonebook, passes it through context(dict). In my template, I have used a for loop to go through all the phonebooks and print them.
I am unable to direct the page to a detailed view. How do I get the phonebook the user clicked on? And how to direct the page from ./view to ./detail
# view.py
def view_phone_book(request):
all_phone_books = PhoneBook.objects.all()
context = {
'all_phone_books': all_phone_books
}
return render(request, "CallCenter/view_phone_book.html", context)
def detailed_view_phone_book(request):
all_contacts = Contact.objects.all().filter(phone_book=phone_book_user_clicked_on)
context = {
'all_contacts': all_contacts
}
return render(request, "CallCenter/detailed_view_phone_book.html", context)
# urls.py
urlpatterns = [
path('', index, name="index"),
path('create/', create_phone_book, name="create"),
path('add/', add_to_phone_book, name="add"),
path('view/', view_phone_book, name="view"),
path('detail/', detailed_view_phone_book, name="detailed_view")
]
# models.py
class PhoneBook(models.Model):
"""
Model to store customer to a phone book
"""
name = models.CharField(max_length=10, blank=False)
def __str__(self):
return self.name
class Contact(models.Model):
"""
Model to store customer to a phone book.
"""
first_name = models.CharField(max_length=50, blank=False)
last_name = models.CharField(max_length=50, blank=False)
phone_number = models.CharField(max_length=13, blank=False, unique=True)
phone_book = models.ForeignKey(PhoneBook, on_delete=models.CASCADE)
def __str__(self):
return self.phone_number
<!--view_phone_book.html-->
<table>
<tr>
<th>Phone Book</th>
</tr>
{% for phone_book in all_phone_books %}
<tr>
<form method="get" action="../detail/"><td>{{ phone_book }} </td></form>
</tr>
{% endfor %}
</table>
<!--detailed_view_phone_book.html-->
<table>
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>Phone Number</th>
</tr>
{% for phone_detail in all_phone_detail %}
<tr>
<form>
<td>{{ phone_detail.first_name }}</td>
<td>{{ phone_detail.last_name }}</td>
<td>{{ phone_detail.phone_number }}</td>
</form>
</tr>
{% endfor %}
</table>
I am unable to go from ./view to ./detail. Also, how would I know which phone book the user clicked on?
I figured it out on how to make it work, and I'm answering it so that if anyone gets stuck in, it can help themselves.
# views.py
def view_phone_book(request):
all_phone_books = PhoneBook.objects.all()
context = {
'all_phone_books': all_phone_books
}
return render(request, "CallCenter/view_phone_book.html", context)
def detailed_view_phone_book(request, phone_book_id):
try:
all_contacts = Contact.objects.filter(pk=phone_book_id)
except Contact.DoesNotExist:
raise Http404("PhoneBook Does Not Exist!")
context = {
'all_contacts': all_contacts
}
return render(request, "CallCenter/detailed_view_phone_book.html", context)
#urls.py
urlpatterns = [
path('', index, name="index"),
path('create/', create_phone_book, name="create"),
path('add/', add_to_phone_book, name="add"),
path('campaign/', create_campaign, name="create-campaign"),
path('view/', view_phone_book, name="view-phone-book"),
path('detail/<int:phone_book_id>', detailed_view_phone_book, name="detail-view-phone-book"),
<!--view_phone_book.html-->
<body>
{% for phone_book in all_phone_books%}
{{ phone_book }}
<br>
{% endfor %}
Back To Home
</body>
<!--detailed_view_phone_book.html-->
{% if all_contacts %}
<table>
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>Phone Number</th>
</tr>
{% for contact in all_contacts %}
<tr>
<form>
<td>{{ contact.first_name }}</td>
<td>{{ contact.last_name }}</td>
<td>{{ contact.phone_number }}</td>
</form>
</tr>
{% endfor %}
</table>
{% endif %}
Back To Home
I watched the Brain's CS50 video, which helped me. I'll suggest you do the same. He explains the concepts in a beginner-friendly way.