Django - How to get admin url from model instance - python

I'm trying to send an email to a user when a new model instance is saved and I want the email to include a link to the admin page for that model instance. Is there a way to get the correct URL? I figure Django must have that information stored somewhere.

Not trying to rip off #JosvicZammit, but using ContentType is the wrong approach here. It's just a wasted DB query. You can get the require info from the _meta attribute:
from django.urls import reverse
info = (model_instance._meta.app_label, model_instance._meta.model_name)
admin_url = reverse('admin:%s_%s_change' % info, args=(model_instance.pk,))

This Django snippet should do:
from django.urls import reverse
from django.contrib.contenttypes.models import ContentType
from django.db import models
class MyModel(models.Model):
def get_admin_url(self):
content_type = ContentType.objects.get_for_model(self.__class__)
return reverse("admin:%s_%s_change" % (content_type.app_label, content_type.model), args=(self.id,))
The self refers to the parent model class, i.e. self.id refers to the object's instance id. You can also set it as a property on the model by sticking the #property decorator on top of the method signature.
EDIT: The answer by Chris Pratt below saves a DB query over the ContentType table. My answer still "works", and is less dependent on the Django model instance._meta internals. FYI.

This gives the same result as Josvic Zammit's snippet, but does not hit the database:
from django.urls import reverse
from django.db import models
class MyModel(models.Model):
def get_admin_url(self):
return reverse("admin:%s_%s_change" % (self._meta.app_label, self._meta.model_name), args=(self.id,))

Just use this one liner that is also python 3 ready:
from django.urls import reverse
reverse('admin:{0}_{1}_change'.format(self._meta.app_label, self._meta.model_name), args=(self.pk,))
More on this in the django admin site doc, reversing admin urls.

So, combining the answers by Chris, Josvic and Josh, here's a copy-paste method you can add into your model (tested on Django 1.8.3).
def get_admin_url(self):
"""the url to the Django admin interface for the model instance"""
from django.core.urlresolvers import reverse
info = (self._meta.app_label, self._meta.model_name)
return reverse('admin:%s_%s_change' % info, args=(self.pk,))

Related

Django renew field from database,calculated field in Database

Newby in django, have two question, can't find needed info.
1) I have database (SQLite) which have table scale_calibration and field weight. Other application rewrite value in field weight 1-2 times per second. Is there possibility in Django to renew this field without renew browser (F5)?
models.py:
from django.db import models
class Calibration(models.Model):
mean_weight = models.FloatField(editable=True)
hours_to_export = models.PositiveIntegerField(default=4, editable=True)
weight = models.FloatField(editable=True)
admin.py:
from django.contrib import admin
from .models import Calibration
# Register your models here.
admin.site.register(Calibration)
2) I try follow that link to make easy calculated field (that will be write to database when save), but i have no results and no error, don't understand where i did mistake.
models.py:
from django.db import models
class Calibration(models.Model):
mean_weight = models.FloatField(editable=True)
hours_to_export = models.PositiveIntegerField(default=4, editable=True)
weight = models.FloatField(editable=True)
calibration_factor = models.FloatField(editable=True)
#property
def get_calibration(self):
return self.weight/self.mean_weight
def save(self, *args, **kwarg):
self.calibration_factor = self.get_calibration()
super(Calibration, self).save(*args, **kwarg)
Please help with advise.
As #david-alford mention, AJAX is a good solution to your problem. This simply writing JavaScript in your templates that make a request every n seconds and update your webpage, also you will need a new endpoint in your Django app that provides the update values from your model to this requests to be repeated.
If this sounds weird or complicated, take a look at some AJAX examples with Django, and feel free to ask more specific questions for clarifications.

How do I test a Django CreateView?

I want to practice testing on Django, and I have a CreateView I want to test. The view allows me to create a new post and I want to check if it can find posts without a publication date, but first I'm testing posts with published date just to get used to syntax. This is what I have:
import datetime
from django.test import TestCase
from django.utils import timezone
from django.urls import reverse
from .models import Post, Comment
# Create your tests here.
class PostListViewTest(TestCase):
def test_published_post(self):
post = self.client.post('/post/compose/', {'author':"manualvarado22", 'title': "Super Important Test", 'content':"This is really important.", 'published_date':timezone.now()})
response = self.client.get(reverse('blog:post_detail'))
self.assertContains(response, "really important")
But I get this:
django.urls.exceptions.NoReverseMatch: Reverse for 'post_detail' with no
arguments not found. 1 pattern(s) tried: ['post/(?P<pk>\\d+)/$']
How do I get the pk for that newly created post?
Thank you!
You can get it directly from the database.
Note, you shouldn't call two views in your test. Each test should only call the code it is actually testing, so this should be two separate views: one to call the create view and assert that the entry is in the db, and one that creates an entry directly and then calls the detail view to check that it displays. So:
def test_published_post(self):
self.client.post('/post/compose/', {'author':"manualvarado22", 'title': "Super Important Test", 'content':"This is really important.", 'published_date':timezone.now()})
self.assertEqual(Post.objects.last().title, "Super Important Test")
def test_display_post(self):
post = Post.objects.create(...whatever...)
response = self.client.get(reverse('blog:post_detail', pk=post.pk))
self.assertContains(response, "really important")
What you wanna do with testing is using the relyable Django Databse API for recevieng the created data and see if your view represents this data.
As you only create 1 model instance and save it. You may obtain its pk via
model_pk = Post.objects.get(author="manualvarado22").pk
This pk then should be inserted into your url as the Exception states.
But i also recommend abseconded test, where you directly check if the newly created "Post" exists in the DB via django model API.
Edit:
When testing, django or the test Module you are using, creates a clean database only for testing and destroys it after the test-run. So if you want to acces a User while testing you must have created the user in your Test setup or in the Test method itself. Otherwise the Usertable would be empty.
I was finally able to solve the issue thanks to your great answers as well as some extra SO research. This is how the test looks like:
def test_display_no_published_post(self):
test_user = User.objects.create(username="newuser", password="securetestpassword")
post = Post.objects.create(author=test_user, title="Super Important Test", content="This is really important.")
response = self.client.get(reverse('blog:post_detail', kwargs={'pk':post.pk}))
self.assertEqual(response.status_code, 404)
And this are the create and detail views:
class PostDetailView(DetailView):
model = Post
def get_queryset(self):
return Post.objects.filter(published_date__lte=timezone.now())
class PostCreateView(LoginRequiredMixin, CreateView):
login_url = '/login/'
redirect_field_name = 'blog/post_detail.html'
form_class = PostForm
model = Post

Django Haystack Custom Search Form

I've got a basic django-haystack SearchForm working OK, but now I'm trying to create a custom search form that includes a couple of extra fields to filter on.
I've followed the Haystack documentation on creating custom forms and views, but when I try to view the form I can only get the error:
ValueError at /search/calibration/
The view assetregister.views.calibration_search didn't return an HttpResponse object. It returned None instead.
Shouldn't basing this on SearchForm take care of returning a HttpResponse object?
forms.py
from django import forms
from haystack.forms import SearchForm
class CalibrationSearch(SearchForm):
calibration_due_before = forms.DateField(required=False)
calibration_due_after = forms.DateField(required=False)
def search(self):
#First we need to store SearchQuerySet recieved after / from any other processing that's going on
sqs = super(CalibrationSearch, self).search()
if not self.is_valid():
return self.no_query_found()
#check to see if any date filters used, if so apply filter
if self.cleaned_data['calibration_due_before']:
sqs = sqs.filter(calibration_date_next__lte=self.cleaned_data['calibration_due_before'])
if self.cleaned_data['calibration_due_after']:
sqs = sqs.filter(calibration_date_next__gte=self.cleaned_data['calibration_due_after'])
return sqs
views.py
from .forms import CalibrationSearch
from haystack.generic_views import SearchView
from haystack.query import SearchQuerySet
def calibration_search(SearchView):
template_name = 'search/search.html'
form_class = CalibrationSearch
queryset = SearchQuerySet().filter(requires_calibration=True)
def get_queryset(self):
queryset = super(calibration_search, self).get_queryset()
return queryset
urls.py
from django.conf.urls import include, url
from . import views
urlpatterns = [
....
url(r'^search/calibration/', views.calibration_search, name='calibration_search'),
....
]
Haystack's SearchView is a class based view, you have to call .as_view() class method when adding a urls entry.
url(r'^search/calibration/', views.calibration_search.as_view(), name='calibration_search'),
This helped me.
"removing the "page" prefix on the search.html template did the trick, and was a good temporary solution. However, it became a problem when it was time to paginate the results. So after looking around, the solution was to use the "page_obj" prefix instead of "page" and everything works as expected. It seems the issue is that the haystack-tutorial assumes the page object is called "page", while certain versions of django its called "page_obj"? I'm sure there is a better answer - I'm just reporting my limited findings."
See this: Django-Haystack returns no results in search form

Django Admin - how to prevent deletion of some of the inlines

I have 2 models - for example, Book and Page.
Page has a foreign key to Book.
Each page can be marked as "was_read" (boolean), and I want to prevent deleting pages that were read (in the admin).
In the admin - Page is an inline within Book (I don't want Page to be a standalone model in the admin).
My problem - how can I achieve the behavior that a page that was read won't be deleted?
I'm using Django 1.4 and I tried several options:
Override "delete" to throw a ValidationError - the problem is that the admin doesn't "catch" the ValidationError on delete and you get an error page, so this is not a good option.
Override in the PageAdminInline the method - has_delete_permission - the problem here -it's per type so either I allow to delete all pages or I don't.
Are there any other good options without overriding the html code?
Thanks,
Li
The solution is as follows (no HTML code is required):
In admin file, define the following:
from django.forms.models import BaseInlineFormSet
class PageFormSet(BaseInlineFormSet):
def clean(self):
super(PageFormSet, self).clean()
for form in self.forms:
if not hasattr(form, 'cleaned_data'):
continue
data = form.cleaned_data
curr_instance = form.instance
was_read = curr_instance.was_read
if (data.get('DELETE') and was_read):
raise ValidationError('Error')
class PageInline(admin.TabularInline):
model = Page
formset = PageFormSet
You could disable the delete checkbox UI-wise by creating your own custom
formset for the inline model, and set can_delete to False there. For
example:
from django.forms import models
from django.contrib import admin
class MyInline(models.BaseInlineFormSet):
def __init__(self, *args, **kwargs):
super(MyInline, self).__init__(*args, **kwargs)
self.can_delete = False
class InlineOptions(admin.StackedInline):
model = InlineModel
formset = MyInline
class MainOptions(admin.ModelAdmin):
model = MainModel
inlines = [InlineOptions]
Another technique is to disable the DELETE checkbox.
This solution has the benefit of giving visual feedback to the user because she will see a grayed-out checkbox.
from django.forms.models import BaseInlineFormSet
class MyInlineFormSet(BaseInlineFormSet):
def add_fields(self, form, index):
super().add_fields(form, index)
if some_criteria_to_prevent_deletion:
form.fields['DELETE'].disabled = True
This code leverages the Field.disabled property added in Django 1.9. As the documentation says, "even if a user tampers with the field’s value submitted to the server, it will be ignored in favor of the value from the form’s initial data," so you don't need to add more code to prevent deletion.
In your inline, you can add the flag can_delete=False
EG:
class MyInline(admin.TabularInline):
model = models.mymodel
can_delete = False
I found a very easy solution to quietly avoid unwanted deletion of some inlines. You can just override delete_forms property method.
This works not just on admin, but on regular inlines too.
from django.forms.models import BaseInlineFormSet
class MyInlineFormSet(BaseInlineFormSet):
#property
def deleted_forms(self):
deleted_forms = super(MyInlineFormSet, self).deleted_forms
for i, form in enumerate(deleted_forms):
# Use form.instance to access object instance if needed
if some_criteria_to_prevent_deletion:
deleted_forms.pop(i)
return deleted_forms

Validation to site.domain fieald

I import
from django.contrib.sites.models import Site in models.py file.
In have this following in admin.py file:
class SitesAdmin(admin.ModelAdmin):
pass
admin.site.unregister(Site)
admin.site.register(Site, SitesAdmin)**
I want to attach validation to the site.domain field in admin.py, How i can accomplish this? please help.
First, specifying an empty ModelAdmin class is unnecessary, the following will work if you don't need to customize the admin:
admin.site.register(Site) # Notice that no ModelAdmin is passed
Now, to your question. You have to create a custom form. Then, you override the clean_domain method of the ModelForm. You can validate any field with the method(s) clean_FOO, where FOO is the field name.
from django import forms
class SiteAdminForm(forms.ModelForm):
def clean_domain(self):
domain = self.cleaned_data.get('domain')
# Custom validation here
return domain
class SiteAdmin(admin.ModelAdmin):
form = SiteAdminForm
admin.site.unregister(Site)
admin.site.register(Site, SiteAdmin)

Categories

Resources