I have an upload dashboard where it displays the users files. The files are represented by a model, and there a view for the dashboard, and a second for the delete action.
Here is the model for the file upload and the related model client:
#python_2_unicode_compatible
class ClientUpload(models.Model):
client = models.ForeignKey(Client)
created_at = models.DateTimeField(auto_now_add=True)
file_upload = models.FileField(upload_to=generate_filename)
def __str__(self):
return self.client.company
class Meta:
verbose_name_plural = _("Client Uploads")
verbose_name = _("Client Upload")
#python_2_unicode_compatible
class Client(models.Model):
user = models.OneToOneField(User)
company = models.CharField(max_length=100)
def __str__(self):
return self.company
class Meta:
verbose_name_plural = _("Clients")
verbose_name = _("Client")
permissions = (
("can_upload", _("Can upload files.")),
("can_access_uploads", _("Can access upload dashboard.")),
("is_client", _("Is a client.")),
)
Here is the url pattern for the delete view:
url(r'^dashboard/delete/(?P<upload_id>\d+)$', views.dashboard_delete, name='dashboard-delete'),
Here is the view for the dashboard and the dashboard delete action:
def dashboard_delete(request, upload_id):
p = get_object_or_404(ClientUpload, pk=upload_id)
p.delete()
return HttpResponseRedirect(reverse('dashboard'))
#login_required(login_url='/dashboard-login/')
def dashboard(request):
current_user = request.user
current_client = request.user.client
files = ClientUpload.objects.filter(client=current_client)
if request.method == 'POST':
form = UploadFileForm(request.POST, request.FILES)
if form.is_valid():
dz_files = request.FILES.getlist('file_upload')
for f in dz_files:
new_file = ClientUpload(client=current_client, file_upload=f)
new_file.save()
logger = logging.getLogger(__name__)
logger.info("File uploaded from " + current_client.company)
else:
logger = logging.getLogger(__name__)
logger.warning("Upload Failed")
return HttpResponseRedirect(reverse('dashboard'))
else:
form = UploadFileForm()
data = {'form': form, 'client': current_client, 'files': files}
return render_to_response('dashboard.html', data, context_instance=RequestContext(request))
and finally the section of the template for the file listing where the dashboard-delete view is called from:
<table class="table">
<tr>
<th>{% blocktrans %}Filename{% endblocktrans %}</th>
<th>{% blocktrans %}Size (Bytes){% endblocktrans %}</th>
<th>{% blocktrans %}Upload Time{% endblocktrans %}</th>
<th>{% blocktrans %}Actions{% endblocktrans %}</th>
</tr>
{% for file in files %}
{% with uploaded_file=file.file_upload %}
<tr>
<th>{{ uploaded_file.name|pathend }}</th>
<th>{{ uploaded_file.size }}</th>
<th>{{ file.created_at }}</th>
<th><i class="fa fa-search"></i><i class="fa fa-trash-o"></i></th>
{% endwith %}
{% endfor %}
</tr>
</table>
In actions there is an icon with an anchor to the dashboard-delete named url. I pass in the current files id. And then in the url.py file I pass it in the regex. The view then finds and deletes the file. I use get_object_or_404() and get no 404 but the page just refreshes and nothing is deleted. For some reason when I press the delete icon I get two requests to /dashboard/delete/5 (5 would be the upload_id for that file) rather than just one.
Any help would be hugely appreciated, why are my models not being deleted, the page reloads and that's it. I cannot find what is wrong. I tried using catch redirect with the dj debug toolbar but it doesn't capture the redirect back to dashboard from dashboard-delete view. Which makes no sense, ordinarily it would catch the redirect.
If you need more information or need to see more code let me know and I will post it but you should have what you need to figure it out. I've been stuck here for a week so I could really use some help to finally get his done.
I know the ID's are correct because they are in the Admin panel as the same numbers and when I hover over I see which ID each item is and they line up with the Admin panel's ID's. I also tried doing {% url 'dashboard-delete' upload_id=file.id %} and it didn't work either, I don't think it has to be a kwarg in this case anyway.
I am completely stumpped here. I've done this exact method many times and it has always worked. What am I missing?
EDIT:
After further testing, it seems the view is never even getting called which makes no sense. When I hover over the link I get the correct URL, but the view never gets called. I put a logging line at the top and it never occurs. Why would this be? What would cause it not to be called?
Noticed that it seems the view doesn't even get called at all. I cant even execute a log statement from inside on the first line. Why would this happen? I've used the same delete views before, and I even tried a CBV with DeleteView.
I had to move my /dashboard/delete above /dashboard in urlpatterns, I knew this and had forgotten. Works now. Stupid me.
Related
I built a portal, where members can see other users' profiles and can like them.
I want to show a page where the currently logged-in users can see a list of profiles only of the members they liked.
The Model has a filed 'liked', where those likes of each member profile are stored:
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
image = models.ImageField(default='default.jpg', upload_to='profile_pics')
company = models.CharField(max_length=500, blank = True)
city = models.CharField(max_length=100, blank = True)
website = models.URLField(max_length=500, blank = True)
liked = models.ManyToManyField(User, related_name='user_liked', blank=True)
My views.py, and here I only show all members so on my template I can loop through each member in members... Including 'member.profile' details from the Profile model.
#login_required
def all_fav_members(request):
users = User.objects.all
context = {'members':users}
return render(request, 'club/all_fav_members.html', context)
I've tried many things, both under views.py and my HTML template, but I was not able to loop through all users associated with a specific Profile under the 'liked' field where that user is equal to request.user.
I'm new to Django, hence trying multiple things. The outcome usually is I get the whole list of members, not the ones current user liked.
One of the not working examples:
{% if member.profile.liked.filter(id=request.user.id).exists()%}
My template:
{% for member in members %}
<table class="table w-100 table-hover">
<thead>
<tr id="header-paragraph-table-top">
<th>Name & Surname</th>
<th>Email</th>
<th>Company</th>
</tr>
</thead>
<tbody>
<tr id="paragraph-table">
<td>{{ member.first_name|capfirst }} {{ member.last_name|capfirst }}</td>
<td>{{ member.email }}</td>
<td>{{ member.profile.company }}</td>
</tr>
</tbody>
</table>
urls.py
path('all_fav_members/', views.all_fav_members, name='all_fav_members'),
I would probably use template tags to solve this issue.
Read this page to get to know how to register template tags: https://docs.djangoproject.com/en/4.0/howto/custom-template-tags/
Inside your_template_tags:
from django import template
register = template.Library()
#register.filter
def liked_user(user, other_user):
return user.profile.liked.filter(id=other_user.id).exists()
Inside your template you could do the following:
{% load your_template_tags %}
{% if member|liked_user:request.user %}
Although I would probably handle it in the views.py like this:
for member in context["members"]:
member.liked_by_user = member.profile.liked.filter(id=request.user.profile.id).exists()
Then you could just use this property in your template like:
{% if member.liked_by_user %}
I'm trying to set up something using Django 1.11 and python for my job where it will display a list of projects submitted by the logged in user. The user can click on the Project name and it will take the user to that project to edit it if they need to. I have it showing the list of projects but when the user clicks the name I get an error. It works fine when there is only 1 project displaying for that users. The error and my code is below.
MultipleObjectsReturned at /edit/
get() returned more than one Project -- it returned 2!
Request Method: GET
Request URL: http://127.0.0.1:8000/edit/
Django Version: 1.11
Exception Type: MultipleObjectsReturned
Exception Value:
get() returned more than one Project -- it returned 2!
url.py
urlpatterns = [
url(r'^index/',views.Index.as_view(),name='index'),
url(r'^form/', views.Form.as_view(), name = 'form'),
url(r'^edit/', views.EditProject.as_view(), name = 'editProject'),
]
views.py
class Index(LoginRequiredMixin,ListView):
template_name = 'sitename/index.html'
form_class = IndexForm
def get_queryset(self):
return Project.objects.filter(user=self.request.user)
class EditProject(LoginRequiredMixin, UpdateView):
template_name = 'irbSite/form.html'
form_class = ProjectForm
success_url = reverse_lazy('irbSite:index')
# works only when there is 1 project object
def get_object(self):
return Project.objects.get(user=self.request.user)
index.html
{% if user.is_authenticated %}
{% if project_list %}
<tr>
<th>Project Number</th>
<th>Project Name</th>
<th>Is Complete</th>
<th>Is Approved</th>
</tr>
{% for project in project_list %}
<tr>
<td><a href="{% url 'irbSite:editProject'%}">{{project.project_id}}</td>
<td>{{project.project_name}}</a></td>
<td>{{project.is_complete}}</td>
<td>{{project.is_approved}}</td>
{% endfor %}
</tr>
{% else %}
<p>You dont have any current IRB forms. Select "Submit New Project" on the left to start one.<p>
use .filter to get multiple object while querying.
Ex:
def get_object(self):
return Project.objects.filter(user=self.request.user)
I have a table inside a form, generated by a formset.
In this case, my problem is to save all the items after one of them is modified, adding a new "virtual" column as the sum of other two (that is only generated when displaying the table, not saved).
I tried different ways, but no one is working.
Issues:
This save is not working at all. It worked when it was only one form, but not for the formset
I tried to generate the column amount as a Sum of box_one and box_two without success. I tried generating the form this way too, but this is not working:
formset = modelformset_factory(
Item, form=ItemForm)(queryset=Item.objects.order_by(
'code__name').annotate(amount=Sum('box_one') + Sum('box_two')))
This issue is related to this previous one, but this new one is simpler:
Pre-populate HTML form table from database using Django
Previous related issues at StackOverflow are very old and not working for me.
I'm using Django 2.0.2
Any help would be appreciated. Thanks in advance.
Current code:
models.py
class Code(models.Model):
name = models.CharField(max_length=6)
description = models.CharField(max_length=100)
def __str__(self):
return self.name
class Item(models.Model):
code = models.ForeignKey(Code, on_delete=models.DO_NOTHING)
box_one = models.IntegerField(default=0)
box_two = models.IntegerField(default=0)
class Meta:
ordering = ["code"]
views.py
class ItemForm(ModelForm):
description = CharField()
class Meta:
model = Item
fields = ['code', 'box_one', 'box_two']
def save(self, commit=True):
item = super(ItemForm, self).save(commit=commit)
item.box_one = self.cleaned_data['box_one']
item.box_two = self.cleaned_data['box_two']
item.code.save()
def get_initial_for_field(self, field, field_name):
if field_name == 'description' and hasattr(self.instance, 'code'):
return self.instance.code.description
else:
return super(ItemForm, self).get_initial_for_field(
field, field_name)
class ItemListView(ListView):
model = Item
def get_context_data(self, **kwargs):
data = super(ItemListView, self).get_context_data()
formset = modelformset_factory(Item, form=ItemForm)()
data['formset'] = formset
return data
urls.py
app_name = 'inventory'
urlpatterns = [
path('', views.ItemListView.as_view(), name='index'),
item_list.html
...
<div>
<form action="" method="post"></form>
<table>
{% csrf_token %}
{{ formset.management_form }}
{% for form in formset %}
<thead>
<tr>
{% if forloop.first %}
<th>{{ form.code.label_tag }} </th>
<th>{{ form.description.label_tag }} </th>
<th> <label>Amount:</label> </th>
<th>{{ form.box_one.label_tag }} </th>
<th>{{ form.box_two.label_tag }} </th>
{% endif %}
</tr>
</thead>
<tbody>
<tr>
<td>{{ form.code }}</td>
<td>{{ form.description }}</td>
<td>{{ form.amount }}</td>
<td>{{ form.box_one }}</td>
<td>{{ form.box_two }}</td>
</tr>
</tbody>
{% endfor %}
<input type="submit" value="Update" />
</table>
</form>
</div>
...
Annotating query with virtual column
Sum is an aggregate expression and is not how you want to be annotating this query in this case. Instead, you should use an F exrepssion to add the value of two numeric fields
qs.annotate(virtual_col=F('field_one') + F('field_two'))
So your corrected queryset would be
Item.objects.order_by('code__name').annotate(amount=F('box_one') + F('box_two'))
The answer provided by cezar works great if intend to use the property only for 'row-level' operations. However, if you intend to make a query based on amount, you need to annotate the query.
Saving the formset
You have not provided a post method in your view class. You'll need to provide one yourself since you're not inheriting from a generic view that provides one for you. See the docs on Handling forms with class-based views. You should also consider inheriting from a generic view that handles forms. For example ListView does not implement a post method, but FormView does.
Note that your template is also not rendering form errors. Since you're rendering the formset manually, you should consider adding the field errors (e.g. {{ form.field.errors}}) so problems with validation will be presented in the HTML. See the docs on rendering fields manually.
Additionally, you can log/print the errors in your post method. For example:
def post(self, request, *args, **kwargs):
formset = MyFormSet(request.POST)
if formset.is_valid():
formset.save()
return SomeResponse
else:
print(formset.errors)
return super().post(request, *args, **kwargs)
Then if the form does not validate you should see the errors in your console/logs.
You're already on the right path. So you say you need a virtual column. You could define a virtual property in your model class, which won't be stored in the database table, nevertheless it will be accessible as any other property of the model class.
This is the code you should add to your model class Item:
class Item(models.Model):
# existing code
#property
def amount(self):
return self.box_one + self.box_one
Now you could do something like:
item = Item.objects.get(pk=1)
print(item.box_one) # return for example 1
print(item.box_two) # return for example 2
print(item.amount) # it will return 3 (1 + 2 = 3)
EDIT:
Through the ModelForm we have access to the model instance and thus to all of its properties. When rendering a model form in a template we can access the properties like this:
{{ form.instance.amount }}
The idea behind the virtual property amount is to place the business logic in the model and follow the approach fat models - thin controllers. The amount as sum of box_one and box_two can be thus reused in different places without code duplication.
I have an upload dashboard with an upload form and a table where it shows files. In the final column are actions such as delete. But when I press delete it deletes the model in the database, but the file is still in the folder. I want the file deleted from this folder too, and it would be even nicer if I could move the deleted file to another directory something like a recycle bin so admins can see files even when they are deleted by the user who uploaded them. (The userbase is under 10 so it can be simple)
Other than that, I was wonderng if there is a way to include in the pre_delete a confirmation alert box of some sort, so that when they press delete it asks in a dialogue for confirmation.
Here is the code I have so far:
My Views one for the dashboard and one for the for deletion:
#login_required(login_url='/dashboard-login/')
def dashboard(request):
current_user = request.user
current_client = request.user.client
files = ClientUpload.objects.filter(client=current_client)
if request.method == 'POST':
form = UploadFileForm(request.POST, request.FILES)
if form.is_valid():
dz_files = request.FILES.getlist('file_upload')
for f in dz_files:
new_file = ClientUpload(client=current_client, file_upload=f)
new_file.save()
logger = logging.getLogger(__name__)
logger.info("File uploaded from " + current_client.company)
else:
logger = logging.getLogger(__name__)
logger.warning("Upload Failed")
return HttpResponseRedirect(reverse('dashboard'))
else:
form = UploadFileForm()
data = {'form': form, 'client': current_client, 'files': files}
return render_to_response('dashboard.html', data, context_instance=RequestContext(request))
def dashboard_delete(request, upload_id):
current_user = request.user
current_client = request.user.client
form = UploadFileForm()
p = ClientUpload.objects.get(pk=upload_id)
p.delete()
files = ClientUpload.objects.filter(client=current_client)
data = {'form': form, 'client': current_client, 'files': files}
return render_to_response('dashboard.html', data, context_instance=RequestContext(request))
My Model and signal:
#python_2_unicode_compatible
class ClientUpload(models.Model):
client = models.ForeignKey(Client)
created_at = models.DateTimeField(auto_now_add=True)
file_upload = models.FileField(upload_to=generate_filename)
def __str__(self):
return self.client.company
class Meta:
verbose_name_plural = _("Client Uploads")
verbose_name = _("Client Upload")
#receiver(post_delete, sender=ClientUpload)
def clientupload_delete(sender, instance, **kwargs):
if instance.file:
# Pass false so FileField doesn't save the model.
instance.file.delete(False)
else:
# log failure
My template for the file list:
{% load i18n %}
{% load staticfiles %}
{% load sasite_filters %}
<table class="table">
<tr>
<th>{% blocktrans %}Filename{% endblocktrans %}</th>
<th>{% blocktrans %}Size (Bytes){% endblocktrans %}</th>
<th>{% blocktrans %}Upload Time{% endblocktrans %}</th>
<th>{% blocktrans %}Actions{% endblocktrans %}</th>
</tr>
{% for file in files %}
{% with uploaded_file=file.file_upload %}
<tr>
<th><a href='{{ uploaded_file.url }}'>{{ uploaded_file.name|pathend}}</a></th>
<th>{{ uploaded_file.size }}</th>
<th>{{ file.created_at }}</th>
<th>Delete</th>
{% endwith %}
{% endfor %}
</tr>
</table>
My urls for the dashboard's delete:
url(r'^dashboard/delete/(?P<upload_id>\d+)$', views.dashboard_delete, name='dashboard-delete'),
Is this all I need to do to ensure the file is deleted? Did I use the signal correctly? Am I missing a step? I just want to make sure, because I'm trying to add to the signal that I want the file to be copied to a "Recycle Bin" directory before I erase it, it doesn't need a model at this point, can just be a file in the directory. But in any case I want it deleted from it's upload_to directory and moved to another directory, before I finish up deleting the model.
Is pre or post delete the correct one to be using here? And for the confirmation box for deletion? Should I use pre_delete or should I be using JavaScript for a confirmation dialogue? I am unsure. Any examples/suggestions are of big help.
Thanks in advance.
There was a typo this works fine:
#receiver(post_delete, sender=ClientUpload)
def clientupload_postdelete(sender, instance, **kwargs):
if instance.file_upload:
filename = os.path.basename(instance.file_upload.path)
client = instance.client.company
folder_path = os.path.join(settings.MEDIA_ROOT, 'uploads/Recycle/', client + '/')
if not os.path.exists(folder_path):
os.makedirs(folder_path)
if os.path.exists(os.path.join(folder_path, filename)):
filename = append_id(filename)
shutil.move(instance.file_upload.path, os.path.join(folder_path, filename))
logger = logging.getLogger(__name__)
logger.info("File %s moved to %s" % (filename, settings.MEDIA_ROOT + '/uploads/Recycle/' + client + '/'))
# Pass False so FileField doesn't save the model.
instance.file_upload.delete(False)
else:
logger = logging.getLogger(__name__)
logger.warning("Failed to find file for copying to %s." % (settings.MEDIA_ROOT + '/uploads/Recycle/'))
My problem is simple: I have Users who own Assets or Assets which belong to Users If you prefer and I cannot make it to retrieve the number (count) of Assets each User has. I know this might be sound silly to most of you but I am new to python/django (coming from PHP/MySQL) and I do not know how things work here. I do not want to be engaged with raw SQL - this would be my last choice If nothing else works.
(*) I have removed all non-related raws from the code
Users
class Users(models.Model):
firstname = models.CharField(max_length=100)
lastname = models.CharField(max_length=100)
Assets
class Assets(models.Model):
serial = models.CharField(unique=True, max_length=100)
user = models.ForeignKey('Users', blank=True, null=True)
# this is what I am playing with to retrieve the number of assets each user owns
#classmethod
def user_assets(self):
return Assets.objects.filter(user=user).count()
views.py
class UserList(ListView):
model = Users
def get_context_data(self, **kwargs):
context = super(UserList, self).get_context_data(**kwargs)
context['user_assets'] = self.model.user_assets()
return context
template
{% for user in object_list %}
<tr>
<td>{{ user.id }}</td>
<td>
{{ user_assets }}
</td>
</tr>
{% endfor %}
How can I get that number? I have read about aggregations, annotations and filters but can't really get it.
EDIT:
I am looking for a simple solution by using class based views and easily expandable (I may want to add other models later)
In your UserList instead using model, use this queryset:
from django.db.models import Count
class UserList(ListView):
queryset = Users.objects.annotate(num_assets=Count('assets'))
and define your user field like so:
user = models.ForeignKey('Users', blank=True, null=True, related_name='assets')
then from template:
{{ user.num_assets }}
Also please remember, it's a good practice to use singular model names, to avoid confusion with reverse relation names..
You are doing weird things. Use the related managers that django give you instead. I'll write the view as a function based view:
views.py
def users_list(request):
object_list = Users.objects.all()
render(request, 'mytemplate.html', { 'object_list': object_list })
You can get the counts directly in the template via the RelatedManager:
mytemplate.html
{% for user in object_list %}
<tr>
<td>{{ user.id }}</td>
<td>
{{ user.assets_set.count }}
</td>
</tr>
{% endfor %}
You could also annotate with a count. But learn to float before you swim :)
BTW, you should call your models "User" and "Asset", not Users and Assets.
You need to use select_related(), count() and pass user instance as argument to class method like so:
#classmethod
def user_assets(cls,user):
return Assets.objects.select_related('Users').filter(user=user).count()
and then use it like so:
user = Users.objects.all()[0] # some user object (this assumes you have at least one user)
Assets.user_assets(user)
this should work fine, you can try it in the shell.
In your context this will be used like this:
user = self.model.all()[0] # or get or filter just get some particular user
context['user_assets'] = Assets.user_assets(user)
EDIT: added links, and Users.object.all() instead of Users.object.get(), also added example suited to your specific use case.