Wagtail: Add method to PageModel to get an inspect url - django

I am trying to add a dynamic instead of a hard coded get_inspect_url method to a PageModel:
class MyModel(Page):
# ...
# this is what I have now, which breaks in prod due to a different admin url
def get_inspect_url(self):
inspect_url = f"/admin/myapp/mymodel/inspect/{self.id}/"
return inspect_url
I know that you can use the ModelAdmin.url_helper_class in wagtail_hooks.py to get admin urls for an object, but I can't figure a way to do the same on the model level as a class method. Is there a way?

thanks to #Andrey Maslov tip I found a way:
from django.urls import reverse
def get_inspect_url(self):
return reverse(
f"{self._meta.app_label}_{self._meta.model_name}_modeladmin_inspect",
args=[self.id],
)
The basic form of a Wagtail admin url is:
[app_label]_[model_name]_modeladmin_[action]
Just change the [action] to one of the following to generated other instance level admin urls:
edit
delete

if you have in you urls.py line like this
path(MyModel/view/<id:int>/', YourViewName, name='mymodel-info'),
then you can add to your models.py this lines
from django.urls import reverse
...
class MyModel(Page):
def get_inspect_url(self):
inspect_url = reverse('mymodel-info', kwargs={'id': self.id})
return inspect_url

Related

Publish a custom Django Flatpage at a set date and time

I have a custom Flatpage model:
from django.contrib.flatpages.models import FlatPage
class MyFlatPage(FlatPage):
publish = models.DateTimeField()
so that I can add a publish date in the future.
Now, I don't have a proper list of flatpages on the front end, my use for frontpages is more like 'one-offs', where I specific the URL and all that. For example, 'about', '2019prize', 'Today's walk', stuff like that.
The urls.py is set up to catch all the flatpages with:
from django.contrib.flatpages import views
re_path(r'^(?P<url>.*/)$', views.flatpage)
How can I set these pages I create to be displayed only after the publish date has arrived? I know that I can filter them by looking up something like pages.filter(publish__lte=now). Where and how should I put that code though?
Additional information
I suppose I need to create a custom view, is that correct? The original view is in ../lib/python3.8/site-packages/django/contrib/flatpages/views.py:
def flatpage(request, url)
if not url.startswith('/'):
url = '/' + url
site_id = get_current_site(request).id
try:
f = get_object_or_404(FlatPage, url=url, sites=site_id)
except Http404:
if not url.endswith('/') and settings.APPEND_SLASH:
url += '/'
f = get_object_or_404(FlatPage, url=url, sites=site_id)
return HttpResponsePermanentRedirect('%s/' % request.path)
else:
raise
return render_flatpage(request, f)
#csrf_protect
def render_flatpage(request, f):
if f.registration_required and not request.user.is_authenticated:
from django.contrib.auth.views import redirect_to_login
return redirect_to_login(request.path)
if f.template_name:
template = loader.select_template((f.template_name, DEFAULT_TEMPLATE))
else:
template = loader.get_template(DEFAULT_TEMPLATE)
f.title = mark_safe(f.title)
f.content = mark_safe(f.content)
return HttpResponse(template.render({'flatpage': f}, request))
How can I extend this, adding my if publish__lte=now code?
What I did is copy-paste the view code from ../lib/python3.8/site-packages/django/contrib/flatpages/views.py to my app.views, rename the two functions, and add the following to render_myflatpage:
def render_myflatpage(request, f):
[...]
if f.publish > now:
f.content = 'This content will be published on ' + str(f.publish)
I then assigned the new view in the catch-all urls.py code:
re_path(r'^(?P<url>.*/)$', myflatpage)
I know this goes against the DRY protocol; this works for me for the time being. If there's a more elegant solution please do let me know.

DJANGO Generic Views: How to use reverse() in get_absolute_url method?

I'm trying to implement generic editing views as shown here:
I started with the CreateView which renders and submits data correctly. However, I am getting an error when I tries to use reverse() to return to the detail view page for the new object.
Here is my Error message:
NoReverseMatch at /work/clients/create/
Reverse for 'ClientDetailView' with arguments '('14',)' and keyword arguments '{}' not found. 0 pattern(s) tried: []
Here is how I defined get_absolute_url() in my model:
def get_absolute_url(self):
return reverse('ClientDetailView', kwargs={'pk': self.pk})
My view is called ClientDetailView. I'm not sure what other information would be helpful.
Here is class ClientDetailView:
class ClientDetailView(generic.DetailView):
model = Client
template_name = 'work/client_detail.html'`
and here is url() from urls.py:
url(r'^clients/(?P<pk>[0-9]+)/$', views.ClientDetailView.as_view(), name='clients_detail'),`
Can anyone explain what I am doing wrong?
I solved my own problem. I had to add the namespace to the reverse() method:
return reverse('work:clients_detail', kwargs={'pk': self.pk})
I would appreciate if someone else could explain why I needed to do this.
Here is the my complete urls.py:
from django.conf.urls import url
from . import views
app_name = 'work'
urlpatterns = [
url(r'^work_orders/$', views.WorkOrdersIndexView.as_view(), name='quotes_index'),
url(r'^work_orders/(?P<pk>[0-9]+)/$', views.WorkOrdersDetailView.as_view(), name='work_orders_detail'),
url(r'^quotes/$', views.QuotesIndexView.as_view(), name='quotes_index'),
url(r'^quotes/(?P<pk>[0-9]+)/$', views.QuotesDetailView.as_view(), name='quotes_detail'),
url(r'^project/(?P<pk>[0-9]+)/$', views.ProjectDetailView.as_view(), name='project_detail'),
url(r'^project/create/$', views.ProjectCreateView.as_view(), name='project_create'),
url(r'^project/(?P<pk>[0-9]+)/update/$', views.ProjectUpdateView.as_view(), name='project_update'),
url(r'^project/(?P<pk>[0-9]+)/delete/$', views.ProjectDeleteView.as_view(), name='project_delete'),
url(r'^clients/$', views.ClientView.as_view(), name='client_index'),
url(r'^clients/(?P<pk>[0-9]+)/$', views.ClientDetailView.as_view(), name='clients_detail'),
url(r'^clients/create/$', views.ClientCreateView.as_view(), name='client_create'),
url(r'^clients/(?P<pk>[0-9]+)/update/$', views.ClientUpdateView.as_view(), name='clients_update'),
url(r'^clients/(?P<pk>[0-9]+)/delete/$', views.ClientDeleteView.as_view(), name='clients_delete'),
]
actually you are trying to reverse the view, instead of ClientDetailView
use url name clients_detail

Django: Get relative uri from views.py

Here is what I have currently:
urls.py:
...
url(r'this/is/relative', 'myapp.views.callview', name='myapp_callview'),
...
views.py:
def callview(request, **kwargs):
# I can get the complete url by doing this
print request.build.absolute_uri() # Prints: https://domain:8080/myapp/this/is/relative
# How do I just get: /myapp/this/is/relative or even /this/is/relative
I would like to extract the relative uri from the view. I could just use regex, but I think there is already something out there that would let me do this.
This will give you "/myapp/this/is/relative":
from django.core import urlresolvers
relative_uri = urlresolvers.reverse("myapp_callview")
Link to Django docs page: https://docs.djangoproject.com/en/dev/ref/urlresolvers/

Properly storing values passed in URL in Django

I have the following URL:
http://localhost:8000/store/add/?points=5
I want to extract the number of points from my URL.
In my views.py, I have:
points = request.GET.get('points',0)
The problem is that it never finds points, so it uses the default of 0.
Any insight into what I'm doing wrong?
My urls.py
from django.conf.urls.defaults import *
from store.models import Part, Category, UniPart, LibraryRequest
from django.views.generic.list_detail import object_list
from voting.views import vote_on_object
urlpatterns=patterns('',
#/store/
url(r'^$','store.views.all_models',name="all_models"),
#/integer/view_part
url(r'^(\d+)/view_part/$','store.views.view_part',name="view_part"),
url(r'^your_models/$','store.views.your_models',name="your_models"),#/your_model
url(r'^user_models/(?P<username>\w+)/$','store.views.user_models',name="user_models"),
url(r'^add/(?P<points>\d+)/','store.views.add_model'),
Snipped from views.py:
def add_model(request, points=None):
print points
Well, your urls.py says that you should use following url /store/add/5, and in your views.py you need to have view
def add_model(request, points=None):
print points
If you want points as a GET parameter change your urls.py as following:
url(r'^add$','store.views.add_model', name='add_model'),
And then you can pass points as /store/add?points=5 and extract it from the URL as
points = request.GET.get('points')
UPDATE
Also in a future to avoid such problems I suggest you to add name parameter to your routes in urls.py (see above) and use reverse in your views (ex. reverse('add_model', kwargs={'points': 5}) and url in your templates (ex. {% url 'add_model' 5 %})
In that case all urls in your application will be generated automatically based on routes in your urls.py files.
Use the following code:
points = request.GET.get('points')
If you are passing more than one variable it can be done in two ways:
METHOD 1:
In your urls.py.
(r'^store/add1/?integer=(?P<category>\d+)/?string=(?P<keyword>\w+)$', 'view_function_name'),
You can get the above values in your views using the following:
def function_name(request):
int_value = request.GET.get('integer')
str_value = reuest.GET.get('string')
And you can also use the pattern given below:
METHOD 2:
In your urls.py.
urlpatterns = patterns('model_name.views',
(r'^store/add1/(?P<integer>\d+)/(?P<string>\w+)/', 'function_name')
)
In your views.py:
def function_name(request, integer= None, string = None):
print integer
print string
#Do whatever you wish with this value
The second method will be more easy for you.

Overriding _get_url() from ImageField

I'm looking to the best way to overriding the _get_url method from ImageField, I need to customize the url since I don't want the default returned url (I distribute this image trhough a view to manage ACL on it so the url based on the MEDIA_ROOT is wrong).
Should I have to create my own ImageField ? or is there a solution using less code ?
Thanks in advance
Fabien
The url returned be _get_url is actually generated by the used Storage class; it would probably make more sense to create your own Storage and use it when creating the ImageField!
See: http://docs.djangoproject.com/en/dev/topics/files/#file-storage, http://docs.djangoproject.com/en/dev/howto/custom-file-storage/
You will neet to override the Storage.url method for that!
Thanks to lazerscience, here my final solution :
from django.core.files.storage import FileSystemStorage
from django.db.models import get_model
from django.core.urlresolvers import reverse
from django.db import models
from django.conf import settings
class PhotographerStorage(FileSystemStorage):
def __init__(self, location=None):
super(PhotographerStorage, self).__init__(location)
def url(self, name):
photo_model = get_model('photographer', 'photo')
photo = photo_model.objects.get(original_image=name)
url = super(PhotographerStorage, self).url(name)
return '%s?size=%d' % (reverse('photographer_photo_display',
args=[photo.slug]), 300)
fs = PhotographerStorage(location=settings.PHOTOGRAPHER_LOCATION)
class Photo(models.Model):
...
original_image = models.ImageField(storage=fs)
...
It works like a charm :)