i make my sitemap and have an interrogation about to get only my objects that have a valid url, explain :
for exemple my events can have divers url, a dedicated page, a simple link to a pdf, a redirection to other page of my site or other site or simply no url.
In my sitemap i do this for only get event that have an url :
def items(self):
events = Event.objects.all()
event_array = []
for event in events:
if event.get_absolute_url():
event_array.append(event)
return event_array
That's work, but i have look at model managers and i think it can do this for me too, so my question is : it is better to have a model manager for this or my way to do this is good?
Thanks :)
Yes, your Model Manager is here to do jobs like this. Create a method that filter all event with an url.
Read this part of the documentation for more details : Django 1.8 - Model Manager
E.g :
from django.db import models
class EventManager(models.Manager):
def get_queryset(self):
return super(EventManager, self).get_queryset().all()
def with_url():
return self.get_query_set().exclude(url__isnull=True, url__exact='')
class Event(models.Model):
objects = EventManager()
Related
I'm in the process of converting an existing Django project to Wagtail. One issue I'm having is email notifications. In the Django project, I have the ability for people to subscribe to a blog, and whenever a new post is published, the author can manually send out a notification to all subscribers in the admin. However, I'm not sure how to accomplish this in Wagtail.
I've read the docs about the page_published signal (https://docs.wagtail.io/en/stable/reference/signals.html#page-published), however, I'm not sure how I could integrate my current code into it. In addition, I would prefer for the author to manually send out the notification, as the author doesn't want to email their subscribers every time a blog post is edited and subsequently published.
For reference, the current code I have for the Django app is as follows (it only works if the blog app is in normal Django; because the blog models are now in the Wagtail app, the current code no longer works).
models.py
class Post(models.Model):
"""Fields removed here for brevity."""
...
def send(self, request):
subscribers = Subscriber.objects.filter(confirmed=True)
sg = SendGridAPIClient(settings.SENDGRID_API_KEY)
for sub in subscribers:
message = Mail(
from_email=settings.FROM_EMAIL,
to_emails=sub.email,
subject="New blog post!",
html_content=( #Abbreviated content here for brevity
'Click the following link to read the new post:' \
'{}'\
'Or, you can copy and paste the following url into your browser:' \
'{}/{}'\
'<hr>If you no longer wish to receive our blog updates, you can ' \
'unsubscribe.').format(
request.build_absolute_uri('/post'),
self.slug,
self.title,
request.build_absolute_uri('/post'),
self.slug,
request.build_absolute_uri('/delete'),
sub.email,
sub.conf_num
)
)
sg.send(message)
admin.py
def send_notification(modeladmin, request, queryset):
for post in queryset:
post.send(request)
send_notification.short_description = "Send selected Post(s) to all subscribers"
#admin.register(Post)
class PostAdmin(SummernoteModelAdmin):
...
actions = [send_notification]
Any suggestions or feedback would be greatly appreciated! Thanks in advance!
EDIT 1
I got a suggestion from the Wagtail Slack to use the register_page_action_menu_item hook (https://docs.wagtail.io/en/stable/reference/hooks.html?highlight=hooks#register-page-action-menu-item). I successfully implemented the action menu item on Wagtail's page editor, however I cannot get my email method to execute (likely due to my not knowing how to properly use the hook). Below is the code from my wagtail_hooks.py file.
from wagtail.admin.action_menu import ActionMenuItem
from wagtail.core import hooks
from .models import Subscriber
from django.conf import settings
from django.core.mail import send_mail
from sendgrid import SendGridAPIClient
from sendgrid.helpers.mail import (Mail, Attachment, FileContent, FileName, FileType, Disposition)
class NotificationMenuItem(ActionMenuItem):
name = 'email-notification'
label = "Notify Subscribers of New Post"
def send(self, request):
"""Used the same def send() method here from my models.py above"""
#hooks.register('register_page_action_menu_item')
def register_notification_menu_item():
return NotificationMenuItem(order=100)
If anyone has advice on how to fix it so it executes, please let me know!
EDIT 2
More problems! (Though I think I'm getting closer.)
Modifying the wagtail_hooks.py to the following, I am able to send an email, but it happens on pageload. So every time I load a blog post in the editor, it sends an email. Clicking the action menu item I created triggers a page reload, which then sends another email (so I don't think my action menu item is actually working when clicked).
Another problem: Because I moved the send() method into the NotificationMenuItem class, I am unable to dynamically generate a blog post's slug and title in the urls of the email.
wagtail_hooks.py
class NotificationMenuItem(ActionMenuItem):
name = 'email-notification'
label = "Notify Subscribers of New Post"
def send(self, request):
"""Used the same def send() method here from my models.py above"""
def get_url(self, request, context):
self.send(request)
EDIT 3
I managed to get the notification system to work in the regular Django admin despite the models being Wagtail models. While this moves the current website's functionality over to the new wagtail site, I still have been unable to solve the most recent issues raised under Edit 2.
Here's the new code in the admin:
def send_notification(modeladmin, request, queryset):
for post in queryset:
post.send(request)
send_notification.short_description = "Send selected Post(s) to all subscribers"
class BlogPageAdmin(admin.ModelAdmin):
list_display = ('title', 'author', 'body')
search_fields = ['title', 'body']
actions = [send_notification]
admin.site.register(BlogPage, BlogPageAdmin)
Better you need to use a post_save signal.
#receiver(post_save, sender=BlogPost)
def send_mail_to_subs(sender, instance, created, **kwargs):
current_site = Site.objects.get_current()
domain = current_site.domain
if created:
for subs in instance.author.subscribed.all():
send_mail(
f'New Post from {instance.author}',
f'Title: {instance.post_title}\nContent: {instance.post_content}\nDate Created: {instance.created}\nUrl: {domain}',
'youremail',
[subs.email],
)
Good coding.
I am using ModelViewSet and Modelserializer for a blog like project.
It could be my difficulty in understanding the implementation; I can't get the update action to work via calling it through router, only the list action is working with the route I have defined.
When I put the url : 127.0.0.1:8000/api/blogs/1, to return the blog with ID 1 to edit, it returns {"Detail": "Not Found."}.
This is my view:
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
I have also overridden the save and update methods in the serializer class, don't know whether it was needed for ModelViewSet in ModelSerializer.
class ArticleSerializer(serializers.ModelSerializer):
def create(self, validated_data):
article = Article.objects.create(
article_title = self.validated_data['article_title'],
article_content = self.validated_data['article_content'],
...
)
return article
def update(self, instance, validated_data):
instance.article_title = validated_data.get('article_title', instance.article_title)
instance.article_content = validated_data.get('article_content', instance.article_content)
...
instance.save()
return instance
class Meta:
model = Article
fields = ...
And the urls.py file:
router = DefaultRouter()
router.register(r'blogs', ArticleViewSet, basename='articles-list')
urlpatterns = router.urls
My question is:
1. How do I specify urls for the ModelViewSet actions (in my case the update action)?
2. Will defining only one url suffice all my needs with every ModelViewSet actions? if so how?
What am I doing wrong? I'm new to DRF.
Regarding your questions:
1) Upon registering ModelViewSet in api router, it will create all required urls for the following actions. In your case it would be following:
list (GET request to /api/blogs/)
retrieve (GET request to
/api/blogs/{pk}/)
create (POST request to /api/blogs/)
update (PUT request to /api/blogs/{pk}/) (it will validate all fields of the model)
partial update (PATCH request to /api/blogs/{pk}/) (it will run no validation - you can send only
fields you've decided to change)
delete (DELETE request to /api/blogs/{pk}/)
So, basically router does most of the job for you about registering viewset actions.
2) I don't completely get it, but if my guess is correct - answer is the same as to first question.
About what you are doing wrong - I am not sure, but did you try appending slash at the end of your request (i.e not /api/blogs/1 but /api/blogs/1/)
Found the issue. I was trying the url localhost/api/blogs/1. It was returning this: "Detail": "Not Found".
It was because there were no instance saved with the id 1. All my saved intance had different ids which i didn't notice before. After putting available ids, it returned desired result.
I am trying to add the content of Django-CMS placeholders to the search index (using Algolia, but I guess this could apply for any indexing service, like Elasticsearch or similar) as soon as they are updated.
Using Django 1.10, django-cms 3.42, I have this model (simplified for this question):
from django.db import models
from cms.models.fields import PlaceholderField
from cms.models import CMSPlugin
class NewsItem(models.Model):
title=models.CharField(_('title'), max_length=200),
content = PlaceholderField('news_content')
I need to do some extra processing as soon as the model field 'content' is saved, and apparently the best way to check for that is to monitor the CMSPlugin model. So I look for saves using from django.db.models.signals.post_save like this:
#receiver(post_save, sender=CMSPlugin)
def test(sender, **kwargs):
logger.info("Plugin saved.")
Now, the problem is that post_save is not triggered as I thought it would. With normal CMS Pages, I noticed that post_save is only triggered when a Page is published, but there is no apparent way to publish a placeholder when used outside the CMS.
The closest similar case I've found is Updating indexes on placeholderfields in real time with django/haystack/solr, but the suggested solution doesn't work.
How could I go about resolving this?
Thank you!
We also had the same search indexing problem when we were implementing djangocms-algolia package, since a placeholder update doesn't trigger an update of the index.
For CMS pages we utilized post_publish and post_unpublish from cms.signals module here.
And for cms apps that use placeholders (eg djangocms-blog) we attached the listeners to post_placeholder_operation, but beware that to make it work your ModelAdmin needs to inherit from PlaceholderAdminMixin:
def update_news_index(sender, operation: str, language: str, **kwargs) -> None:
placeholder: Optional[Placeholder] = None
if operation in (ADD_PLUGIN, DELETE_PLUGIN, CHANGE_PLUGIN, CLEAR_PLACEHOLDER):
placeholder = kwargs.get('placeholder')
elif operation in (ADD_PLUGINS_FROM_PLACEHOLDER, PASTE_PLUGIN, PASTE_PLACEHOLDER):
placeholder = kwargs.get('target_placeholder')
elif operation in (MOVE_PLUGIN, CUT_PLUGIN):
placeholder = kwargs.get('source_placeholder')
else:
pass
if placeholder:
post: Post = Post.objects.language(language_code=language).filter(content=placeholder).first()
if post:
post.save()
signals.post_placeholder_operation.connect(update_news_index, PostAdmin)
signals.post_placeholder_operation.connect(update_news_index, PageAdmin)
I have a model like this:
from wagtail.wagtailcore.models import Page
class Blog(Page):
created = models.DateTimeField(auto_now_add=True)
...
..
Right Now, my default, if my slug is hi-there, the blog post is accessible on site_url:/hi-there/ but I want it accessible via site:url/2014/02/05/hi-there/ The page has various methods like url, url_path which one should I override and what's the best practice to achieve something like this in wagtail?
The RoutablePageMixin is the current (v2.0+) way to accomplish this.
Add the module to your installed apps:
INSTALLED_APPS = [
...
"wagtail.contrib.routable_page",
]
Inherit from both wagtail.contrib.routable_page.models.RoutablePageMixin and wagtail.core.models.Page, then define some view methods and decorate them with the wagtail.contrib.routable_page.models.route decorator:
from wagtail.core.models import Page
from wagtail.contrib.routable_page.models import RoutablePageMixin, route
class EventPage(RoutablePageMixin, Page):
...
#route(r'^$') # will override the default Page serving mechanism
def current_events(self, request):
"""
View function for the current events page
"""
...
#route(r'^past/$')
def past_events(self, request):
"""
View function for the past events page
"""
...
# Multiple routes!
#route(r'^year/(\d+)/$')
#route(r'^year/current/$')
def events_for_year(self, request, year=None):
"""
View function for the events for year page
"""
...
New in Wagtail v0.5 is a mechanism that directly addresses this sort of thing:
Embedding URL configuration in Pages or RoutablePage in v1.3.1
(the docs even have an Blog example!)
I was looking into migrating my blog to a wagtail version and wanted to support my previous url schema and had to solve this exact problem. Luckily I just found a solution and want to share it, hopefully this will be helpful for someone else in the future.
The solution is a 2 Step process.
change the url of a blog page to contain the date as well.
class Blog(Page):
created = models.DateTimeField(auto_now_add=True)
def get_url_parts(self, request=None):
super_response = super().get_url_parts(request)
# handle the cases of the original implementation
if super_response is None:
return None
(site_id, root_url, page_path) = super_response
if page_path == None:
return super_response
# In the happy case, add the date fields
# split prefix and slug to support blogs that are nested
prefix, slug, _ = page_path.rsplit("/", 2)
return (
site_id,
root_url,
f"{prefix}/{self.created.year}/{self.created.month}/{self.created.day}/{slug}/",
)
And now we need to make those posts also route-able.
class BlogIndexPage(RoutablePageMixin, Page):
...
def route(self, request, path_components):
if len(path_components) >= 4:
year, month, day, slug, *rest = path_components
try:
subpage = self.get_children().get(slug=slug)
return subpage.specific.route(request, rest)
except Page.DoesNotExist:
...
return super().route(request, path_components)
This solution ignores the date and just use the slug to locate a blog post as the original solution. This should also work when you don't use the RoutablePageMixin.
Hope this is still helpful for someone.
As much as I love the django documentation, the section on bookmarklets in the admin is strangely vague.
My question is this: If I'm in a view and I have a django model (or, in some cases, an actual object), how can I get to the relevant admin pages for that model (or object)? If I have the object coconut_transportation.swallow.objects.all()[34], how can I jump right to the admin page to edit that particular swallow?
Likewise, how can I get the URL for the admin page to add another swallow?
http://docs.djangoproject.com/en/dev/ref/contrib/admin/#reversing-admin-urls
obj = coconut_transportation.swallow.objects.all()[34]
# list url
url = reverse("admin:coconut_transportation_swallow_changelist")
# change url
url = reverse("admin:coconut_transportation_swallow_change", args=[obj.id])
# add url
url = reverse("admin:coconut_transportation_swallow_add")
You can retrieve this from the actual object instance, this worked for me:
from django.core import urlresolvers
from django.contrib.contenttypes.models import ContentType
content_type = ContentType.objects.get_for_model(object.__class__)
object_admin_url = django.core.urlresolvers.reverse("admin:%s_%s_change" % (content_type.app_label, content_type.model), args=(object.pk,))
See this: http://djangosnippets.org/snippets/1916/
You can actually retrieve the info without making a query to ContentTypes
Just add this to your model class:
def get_admin_url(self):
return urlresolvers.reverse("admin:%s_%s_change" %
(self._meta.app_label, self._meta.model_name), args=(self.pk,))