Wagtail: Creating a custom API endpoint - python

I've created a Snippet called "Spotlights," and I'm wondering how I can create a custom endpoint for Snippet data with the Wagtail API. My best guess would be:
api_router.register_endpoint('Spotlights', BaseAPIEndpoint)
Is the first arg there establishing the name of the endpoint, or referencing something?

I've figured it out: just subclass Wagtail's BaseAPIEndpoint. For example:
endpoints.py
from wagtail.api.v2.endpoints import BaseAPIEndpoint
class SpotlightsAPIEndpoint(BaseAPIEndpoint):
...
model = Spotlight
api.py
from .endpoints import SpotlightsAPIEndpoint
api_router.register_endpoint('spotlights', SpotlightsAPIEndpoint)
Plus there are tons of ways to customize it. Just take a look at the endpoints.py file in the Wagtail repo: https://github.com/wagtail/wagtail/blob/master/wagtail/api/v2/endpoints.py

According to Wagtail documentation, the first parameter is the name of the endpoint (eg. pages, images) and this is used in the URL of the endpoint.
The second parameter is the endpoint class that handles the requests.
For example:
api_router.register_endpoint('pages', PagesAPIEndpoint)
api_router.register_endpoint('images', ImagesAPIEndpoint)
api_router.register_endpoint('documents', DocumentsAPIEndpoint)
So, I advise you to make like:
api_router.register_endpoint('spotlights', BaseAPIEndpoint)

Latest Wagtail version - 2.15 +
in your views file, import your model and BaseApiViewSet
from .models import CustomModel
from wagtail.api.v2.views import BaseAPIViewSet
class BusinessLocationViewSet(BaseAPIViewSet):
model = BusinessLocation
body_fields = BaseAPIViewSet.body_fields + ['id', 'field1', 'field2', 'field3', 'field4', etc, etc...]
and in the api.py file in the project folder, import your model and expose it to the api as follows:
from custommodel.views import MyModel
api_router.register_endpoint('custom', MyModel)
and now your model will be accessible from the api/v2/custom endpoint

I needed to modify glls's solution a bit to make it work. To expose the endpoint in api.py:
from .views import CustomViewSet
api_router.register_endpoint("custom", CustomViewSet)
It's also common to add filtering functionality. In views.py:
from wagtail.images.api.v2.views import BaseAPIViewSet, FieldsFilter
from .models import Custom
class CustomViewSet(BaseAPIViewSet):
model = Custom
body_fields = BaseAPIViewSet.body_fields + [
"name"
]
filter_backends = [
FieldsFilter
]
You can now filter the Custom snippet like normal: /api/v2/custom/?name=something

Related

Django overriding default managers of contrib models

I have a Django app named site_settings in which SiteSettings model is defined. This model contains a foreign key to django.contrib.sites.models.Site model. Now I want to override default objects manager of Site model with new one I defined:
from django.contrib.sites.models import SiteManager as _OrigSiteManager
class SiteManager(_OrigSiteManager):
...
I tried:
Site.add_to_class("objects", SiteManager())
But it didn't work. The problem is I am adding to class with already existing objects name. When adding with another name, it works as expected:
Site.add_to_class("my_objects", SiteManager()) # now Site.my_objects points to my custom manager
But I want to override existing objects manager with my custom manager. How can I do that?
You can redefine and use your own Site class everywhere:
from django.contrib.sites.models import Site
class Site(Site):
...
objects = SiteManager()
or you can use contribute_to_class:
from django.contrib.sites.models import Site
SiteManager().contribute_to_class(Site, 'objects')
or you can made monkey patch:
from django.contrib.sites.models import Site
Site.objects = SiteManager(model=Site)
If you need it only to change queryset in foreignkey to Site, you can use limit_choices_to:
foreignkey(Site, limit_choices_to=Q(your query to limit sites queryset))

Call a function using value from variable

I want to get a model in django using the current url
for example
a = resolve(request.path_info).url_name
context = { 'example' : a.objects.all }
here the retrieved url name is available in models
This obivously won't work but this is what I want to accomplish
You can access the request's resolver via request.resolver_match. See the docs here for more info. That can give you better access to the resolver than looking it up again.
Once you have that you can look up the model using the AppConfig.get_model method.
from django.apps import apps
ModelClass = apps.get_model(app_name, model_name)
Or
ModelClass = apps.get_model(f'{app_name}:{model_name}')

Wagtail: How to overwrite create/edit template for PageModel

I want to overwrite create.html and edit.html used for models derived from Wagtails 'PageModel'.
If I understand the docs correctly it should be as simple as specifying the attributes:
class MyAdmin(ModelAdmin):
model = MyPage
create_template_name = "myapp/create.html"
edit_template_name = "myapp/edit.html"
My templates are located at projectroot/templates/myapp. It works fine if my model is a Django model but for a PageModel based model the create view still uses wagtailadmin/pages/create.html. I also tried the other location patterns mentioned in the docs w/o success.
Is it possible to change the edit and create templates for a PageModel? Or do the same limitations as for views apply, i.e. only index.html and inspect.html can be overwritten?
ModelAdmin does not provide create, edit, or delete functionality for Page models, as per the documentation note.
NOTE: modeladmin only provides ‘create’, ‘edit’ and ‘delete’ functionality for non page type models (i.e. models that do not extend wagtailcore.models.Page). If your model is a ‘page type’ model, customising any of the following will not have any effect.
It can be a bit confusing as the ModelAdmin system would seem to work for page models also, but there are some other ways to modify how your page can be edited. These will not be scoped to the ModelAdmin area though.
Option 1 - customise the generated form for your MyPage model
If you only want to customise how the edit page form that gets generated you can modify the base_form_class on your page model.
Wagtail has documentation about how to create a custom page form.
Note: WagtailAdminPageForm extends Django's ModelFormMetaClass
Example
from django import forms
from django.db import models
from wagtail.admin.forms import WagtailAdminPageForm
from wagtail.core.models import Page
class EventPageForm(WagtailAdminPageForm):
# ...
class MyPage(Page):
# ...
base_form_class = MyPageForm
Option 2 - customise the view via hooks
To customise the create & edit views for the normal (e.g. clicking edit page on the Wagtail user bar or explorer) page editing interface, you will need to use Wagtail hooks. Here you have access to the request so you will likely be able to determine if you are in the ModelAdmin area.
Create a file called wagtail_hooks.py in your app folder and provide a hook that will return a custom response (this will need to be rendered by your custom view.).
There are separate hooks for before_create_page and before_edit_page
Example from before_create_page docs below.
from wagtail.core import hooks
from .models import AwesomePage
from .admin_views import edit_awesome_page
#hooks.register('before_create_page')
def before_create_page(request, parent_page, page_class):
# Use a custom create view for the AwesomePage model
if page_class == AwesomePage:
return create_awesome_page(request, parent_page)
```python

Manager isn't available while setting up django-fmc

I'm trying to get django-fmc set up with Django (v 1.97, Python v2.7.12, djangorestframework v3.3.3) to handle storing registration ids and sending notifications to devices. I am following the tutorial they provide but it doesn't seem to be working.
I am getting the following error when running my local server and python manage.py fcm_urls:
...
File "C:\Work\Dev\LiveTracking\Api\app\views.py", line 50, in DeviceViewSet
queryset = Device.objects.all()
File "C:\Work\Dev\LiveTracking\Api\env\lib\site-packages\django\db\models\manager.py", line 277, in __get__
self.model._meta.swapped,
AttributeError: Manager isn't available; 'fcm.Device' has been swapped for 'app.MyDevice'
I don't want to add additional fields to the MyDevice model for now. I've looked all over but can't fix this error. If anyone can shed some insight into this error it would be much appreciated.
Here are some of my code snippets:
settings.py
INSTALLED_APPS = (
'fcm',
)
# Firebase Cloud Messaging Key
FCM_APIKEY = 'AIzaSyCaqHZIcaGDOpfTZUmAHEowsqD-fCtow6A'
# Location of device model
FCM_DEVICE_MODEL = 'app.MyDevice'
serializers.py
from fcm.models import Device
class DeviceSerializer(serializers.ModelSerializer):
class Meta:
model = Device
fields = ('dev_id','reg_id','name','is_active')
views.py
from rest_framework import viewsets
from fcm.models import Device
from fcm.serializers import DeviceSerializer
class DeviceViewSet(viewsets.ModelViewSet):
queryset = Device.objects.all()
serializer_class = DeviceSerializer
urls.py
from rest_framework import routers
from fcm.views import DeviceViewSet
router = routers.DefaultRouter()
router.register(r'devices', DeviceViewSet)
urlpatterns = [
url(r'^v1/', include(router.urls)),
]
swappable is an undocumented feature, actually only supposed to be used for custom User models. The doc on custom user models clearly states that once you use a custom user model, directly referencing contrib.auth.models.User won't work:
If you reference User directly (for example, by referring to it in a foreign key), your code will not work in projects where the AUTH_USER_MODEL setting has been changed to a different user model.
You probably want to read the rest of this chapter FWIW.
To make a long story short: as Daniel Roseman mentions, you very probably want to use your own MyDevice model instead of the default Device one. And eventually contribute back a patch to django-fcm doc if it solves the issue.

How to correctly integrate Filepicker.io, Django and S3

I am making a Django app which allows users to upload pictures via the admin interface and then access the URL of those images via an API endpoint. On the admin interface, the user should be presented with the Filepicker.io widget (to enable drag and drop functionality, etc.) and the file should be uploaded to S3. I have already entered my S3 credentials into the Filpicker admin page of my account.
My question is how to bring all these elements together. Here the appropriate files from my project:
# models.py
from django.db import models
from django_filepicker.models import FPFileField
# Add field introspection for FPFileField
# See http://south.aeracode.org/wiki/MyFieldsDontWork
from south.modelsinspector import add_introspection_rules
add_introspection_rules([], ["^django_filepicker\.models\.FPFileField"])
class Product(models.Model):
product_logo = FPFileField(upload_to='uploads')
# forms.py
from django import forms
from django_filepicker.forms import FPFileField
from django_filepicker.widgets import FPFileWidget
from example.models import Product
class ProductForm(forms.ModelForm):
class Meta:
model = Product
def __init__(self, *args, **kwargs):
self.fields['product_logo'] = FPFileField(widget = FPFileWidget)
super(ProductForm, self).__init__(*args, **kwargs)
# views.py
from django.http import HttpResponse
from example.models import Product
import json
def example_view():
result = []
products = Product.objects.all()
for product in products:
# I want the S3 URL here!
result.append(product.product_logo.url)
return HttpResponse(json.dumps(result, indent=2))
My problems are:
The Django admin interface displays the normal Django FileField widget, whereas I want the FPFileWidget instead
I want the image files to upload to S3 (not the /media directory of my site)
I want to retrieve the S3 URLs not the /media/xxxx URLs.
For example, a typical response is currently this:
[
"/media/uploads/fp-file",
"/media/uploads/fp-file_1",
"/media/uploads/fp-file_2",
"/media/uploads/fp-file_3",
"/media/uploads/fp-file_4",
"/media/uploads/fp-file_5"
]
But I want something like:
[
"https://s3-ap-southeast-2.amazonaws.com/XXXXXXXXX/TBtOcRSNyBAZZuNBFOpA_blah.png",
"https://s3-ap-southeast-2.amazonaws.com/XXXXXXXXX/8ODleDuKRIOAglFs0sKl_etc.png",
]
You will need to change DEFAULT_STORAGE_BACKEND to an s3 storage backend. Please have a look at amazon-S3 backend of django-storages.
For using FPFileWidget on admin panel, please have a look at Django Admin: Using a custom widget for only one model field.
Changing these two settings should work for you.
Take a look at FPUrlField, that should give you what you need. The current FP*Fields don't have admin interfaces, but would be happy to take a pull request if you want to contribute the code back

Categories

Resources