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)
Related
In Django admin, when you are looking at a record there is a button called "history". When you click on this button, it tells you when the record was created and by whom and also each time a field was changed and by whom. I find this extremely useful. However, I noted that these only show up for actions that are done on the admin page. So when I change the record through a view, this is not displayed in that history. Is there an easy way to have my views store this information also so that it will all be visible from the admin page?
Thanks so much in advance for your help.
The admin app comes with a model - LogEntry. Every time you do something in the admin app, there is some code somewhere that saves a LogEntry. This is how the app works. For example in the changeform_view there is something like this:
def changeform_view(self, request, ...):
...
if request.method == "POST":
if all_valid(formsets) and form_validated:
# do some other stuff then
self.log_change(request, new_object, change_message)
def log_change(self, request, object, message):
"""
Log that an object has been successfully changed.
The default implementation creates an admin LogEntry object.
"""
from django.contrib.admin.models import CHANGE, LogEntry
return LogEntry.objects.log_action(
user_id=request.user.pk,
content_type_id=get_content_type_for_model(object).pk,
object_id=object.pk,
object_repr=str(object),
action_flag=CHANGE,
change_message=message,
)
LogEntry.objects.log_action is where the log is created. Unfortunately, this doesn't happen anywhere else, unless you were to make it happen.
There's nothing stopping you from doing this though. You can create a LogEntry wherever you want.
Having said that, it might be a bit confusing since when you see a LogEntry you now no longer no if that is a change that has happened because of someone manually changing some data via the admin app, or a change that has occurred programatically. It would probably be a better idea to create your own Log model, and save logs where and when you want.
You can always display your Logs in your in the relevant admin view should you so wish. Something like this will do the trick:
class MyAdmin(admin.modelAdmin):
readonly_fields = (logs_field,)
def logs_field(self, instance):
logs = Log.objects.filter(object=instance)
return format_html_join(
'\n', "<p>{}: {}</p>",
((log.date, log.message) for log in logs)
)
I have this function-based view with django-rest-swagger decorated. However, I can't find place in UI that allow me to post the payload (request.body).
I saw a couple solutions about doing it with class-based view, but I was wondering if there is a way to do it with function-based view.
Thank you in advance!
#renderer_classes([JSONRender])
#api_view(['POST'])
def some_method(request):
body = json.loads(request.body)
return JsonResponse({'status': 'ok'})
I am gonna answer my question since the django-rest-swagger was deprecated in June 2019 and I just found out 2 feasible solutions.
First one will change the UI globally.
In ping.views (or any other location you wish) add following class.
from rest_framework.schema import AutoSchema
class CustomSchema(AutoSchema):
def __init__(self):
super(CustomSchema, self).__init__()
def get_manual_fields(self, path, method):
extra_fields = [
coreapi.Field('command', required=True, location='form', schema=String(), description='', type='', example='',
coreapi.Field('params', required=False, location='form', schema=String(), description='', type='', example='',
]
manual_fields = super().get_manual_fields(path, method)
return manual_fields + extra_fields
Add following settings in settings.py of your Django project.
REST_FRAMEWORK = {
# Corresponding path to where you added the class
'DEFAULT_SCHEMA_CLASS': 'ping.views.CustomSchema',
}
Second solution can be applied on a per-view basis. You may check here for official guide
Use #schema from rest_framework.decorators.schema to overwrite DEFAULT_SCHEMA_CLASS.
#api_view(['POST'])
#schema(CustomSchema())
def your_view(request):
print(request.body)
return JsonResponse({'task_status': 200'})
Basically, the idea is to overwrite DEFAULT_SCHEMA_CLASS. The word schema is the term that they used to refer to swagger UI for each view in rest_framework.
When you use #api_view() to decorate your function-based view, it will assign your function an attribute schema with value APIView.schema from rest_framework.views.APIView.
rest_framework.views.APIView will in further call DefaultSchema() to load the DEFAULT_SCHEMA_CLASS from your REST_FRAMEWORK configuration in settings.py.
Without other specifying, DEFAULT_SCHEMA_CLASS is rest_framework.schemas.openapi.AutoSchema by this official announcement. You might want to change it to rest_framework.schemas.coreapi.AutoSchema since it is the one that compatible with django_rest_swagger.
Hope this tutorial helps people who use django-rest-swagger (2.2.0) with function-based views for their Django project.
Please leave comments if there are anything I can help on this issue.
I'm in Django 1.6 and I'd like to alter the admin to show an icon in from of each model name (on the admin's index page).
The problem is I can't alter the template (As far as I know) because I'm creating a third party installable app, and when users install it, presumably they could already have their own styles, and customizations for their admin page. (correct me I'm wrong).
So far I've tried doing this in models.py but it's still escaping the HTML on the admin page:
from django.db import models
from django.utils.safestring import mark_safe
class DataConnection(models.Model):
class Meta:
verbose_name_plural = mark_safe("<img src="..." />My Model")
...
Well this seems to work. I monkey patch the admin.site.index and admin.site.app_index methods with my modified versions that grab the response objects and add in the desired HTML and mark it as safe:
def index(self, *args, **kwargs):
response = admin.site.__class__.index(self, *args, **kwargs)
my_app_context = [d for d in response.context_data['app_list'] if d.get('app_label','') == u'my_app_name']
if my_app_context:
for m in my_app_context[0]['models']:
m['name'] = mark_safe(("<img src='...' /> " + unicode(m['name']))
return response
admin.site.index = index.__get__(admin.site, admin.site.__class__)
#...Do something similar for the admin.site.app_index method
This is a follow up question for Django on Google App Engine: cannot upload images
I got part of the upload of images to GAE Blobstore working. Here's what I did:
In models.py I created a model PhotoFeature:
class PhotoFeature(models.Model):
property = models.ForeignKey(
Property,
related_name = "photo_features"
)
caption = models.CharField(
max_length = 100
)
blob_key = models.CharField(
max_length = 100
)
In admin.py I created an admin entry with an override for the rendering of the change_form to allow for insert of the correct action to the Blobstore upload url:
class PhotoFeatureAdmin(admin.ModelAdmin):
list_display = ("property", "caption")
form = PhotoFeatureForm
def render_change_form(self, request, context, *args, **kwargs):
from google.appengine.ext import blobstore
if kwargs.has_key("add"):
context['blobstore_url'] = blobstore.create_upload_url('/admin/add-photo-feature')
else:
context['blobstore_url'] = blobstore.create_upload_url('/admin/update-photo-feature')
return super(PhotoFeatureAdmin, self).render_change_form(request, context, args, kwargs)
As I use standard Django, I want to use the Django views to process the result once GAE has updated the BlobStore in stead of BlobstoreUploadHandler. I created the following views (as per the render_change_form method) and updated urls.py:
def add_photo_feature(request):
def update_photo_feature(request):
This all works nicely but once I get into the view method I'm a bit lost. How do I get the Blob key from the request object so I can store it with PhotoFeature? I use standard Django, not Django non-rel. I found this related question but it appears not to contain a solution. I also inspected the request object which gets passed into the view but could not find anything relating to the blob key.
EDIT:
The Django request object contains a FILES dictionary which will give me an instance of InMemoryUploadedFile. I presume that somehow I should be able to retrieve the blob key from that...
EDIT 2:
Just to be clear: the uploaded photo appears in the Blobstore; that part works. It's just getting the key back from the Blobstore that's missing here.
EDIT 3:
As per Daniel's suggestion I added storage.py from the djangoappengine project which contains the suggested upload handler and added it to my SETTINGS.PY. This results in the following exception when trying to upload:
'BlobstoreFileUploadHandler' object has no attribute 'content_type_extra'
This is really tricky to fix. The best solution I have found is to use the file upload handler from the djangoappengine project (which is associated with django-nonrel, but does not depend on it). That should handle the required logic to put the blob key into request.FILES, as you'd expect in Django.
Edit
I'd forgotten that django-nonrel uses a patched version of Django, and one of the patches is here to add the content-type-extra field. You can replicate the functionality by subclassing the upload handler as follows:
from djangoappengine import storage
class BlobstoreFileUploadHandler(storage.BlobstoreFileUploadHandler):
"""Handler that adds blob key info to the file object."""
def new_file(self, field_name, *args, **kwargs):
# We need to re-process the POST data to get the blobkey info.
meta = self.request.META
meta['wsgi.input'].seek(0)
fields = cgi.FieldStorage(meta['wsgi.input'], environ=meta)
if field_name in fields:
current_field = fields[field_name]
self.content_type_extra = current_field.type_options
super(BlobstoreFileUploadHandler, self).new_file(field_name,
*args, **kwargs)
and reference this subclass in your settings.py rather than the original.
I have been reading this installation guide to install tinymce with django. First, it has not worked and second, it seems to be linked only with the admin account. Moreover other blogs about tinymce and django integration are also oriented towards admin account only.
I am basically making a blog application and I want to provide some of the basic features of tinymce and not the whole stuff. What should I do? Should I switch over to some other text editor.
i would suggest not doing anything at all on the server-side, include the JS files and use the normal JS-init for creating the widgets.
Try it:
class MyForm(forms.Form):
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(+args, **kwargs)
self.fields['FIELD_NAME'].widget = TinyMCE()
As mentioned in the docs you have to specify the widget in your form class:
from tinymce.widgets import TinyMCE
class MyForm(ModelForm):
html = forms.CharField(widget=TinyMCE())