How to correctly integrate Filepicker.io, Django and S3 - python

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

Related

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

Access Django DB objects without shell?

I have a request - can you help me access and manage django DB objects without using shell ?
I have created my models, but now (for example) i want to make a login system. I store users and passes(again, only an example), and i want to get the info from the DB, but i dont want to use shell.
What can i do in this case, im quite new to Django ?!
Best Regards
Why not use django-admin?
Maybe this is what you want:https://docs.djangoproject.com/en/3.0/ref/contrib/admin/
In views.py you can import
from .models import modelname
data = modelname.objects.all() - using this you can get all the data from the Database
Eg:-
for d in data:
print (d.email)
Will give all emails in the database
You can also use
t = modelname.objects.get(email='name#lk.com')
By this you can get the data of the person who's email is name#lk.com
Django already has database support where you can register your models and access them with a graphical interface.
See the documentation: django-admin-site
First you need to create a super user account, if you don't have one, create it with the terminal in the project directory, use this row:
python manage.py createsuperuser
For your model to appear on the admin site you need to register it
# models.py
class MyModel(models.Model)
field1 = models.CharField()
field2 = models.TextField()
# ...
# admin.py
from django.contrib import admin
from .models import MyModel
admin.site.register(MyModel)
So it's the basic way to register your model, if you want to personalize you need to check the documentation ModelAdmin.fieldsets
with this done, just access the admin site at the link http://localhost:8000/admin/ and log in with the super user account and you will see the model registered.

Wagtail: Creating a custom API endpoint

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

How do you give a wagtail/django Page a custom url to serve at?

In wagtail/django how do you make a basic wagtail Page model, create the html template, and then tell that model to serve as a view for a specific url?
from django.db import models
from wagtail.wagtailcore.models import Page
class MyPage(Page):
title = models.CharField(blank=True, null=True, max_length=255)
#...
I want the url to register like
url(r'^monkey/(?P<slug>[A-Za-z0-9]+)$', ...)
But I don't have a common urls.py folder its stored outside of the project. I tried using the RoutablePageMixin, but I believe that serves for subpages. I also know where to store the html template within the structure so that is not a problem.
You might want something like:
from django.http import Http404
from django.views import View
from wagtail.core.views import serve
class WagtailPageServeView(View):
"""
A default way to serve up wagtail pages in urls
"""
wagtail_slug = None
def get(self, request, *args, **kwargs):
if self.wagtail_slug:
return serve(request, self.wagtail_slug)
else:
raise Http404(
f"WagtailPageServeView missing 'wagtail_slug'. Cannot serve "
f"request.path: {request.path}"
)
Then you can do something like this in urls.py:
urlpatterns += [
re_path(r'^$', WagtailPageServeView.as_view(wagtail_slug='/'), name='home'),
]
In Wagtail, you are creating a page type and then creating instances of the page type in the CMS.
Even if you are only going to use this page once, you still need to make a type. Following this logic, page urls are defined inside the CMS through the page's parent and slug. Most likely, you could achieve what you want through these means.
If you want to override your page template and create a custom url, you can override the get_url_parts method on your model.
Also, if you want to skip Wagtail for this page specifically, remember that Wagtail is just built over Django. You can always create a new module and define the url in that model. If you check your site urls.py, you will see the wagtail url patterns. You can change it to look something like this:
urlpatterns = [
# other urls...
url(r'wagtail/', include(wagtail_urls)),
url(r'^monkey/', ('monkey_app.urls', 'monkey_app')),
]

How should I enforce permission checking when file is reached/downloaded in Django?

I don't know this is a typical thing to do in web application, but what we achieve is that, let's say we have a Person model, inside this model, we have a FileField stores user's photo:
class Person(models.Model):
photo = models.FileField(upload_to='Person_photo')
What I want to achieve is that only the owner can see his or her photo. People who log on a different account or even not log in should not be able to see the photo. Let's assume we have a way to check whether a photo belongs to a certain user or not:
def permission(photo_filename, pid):
# return True if photo_filename exists and belongs to the person pid
We can figure this part out, for example, use permission system provided in Django. Of course from the views.py we can control whatever image we want to show on the page, but for example, we want to block people make attempts to figure out the URLs and get the photo, for example, typing
http://some.domain/media/Person_photo/Amy.jpg
in URL bar in the browser should only work if she is Amy. What is a good way to do it? Is there a library for this purpose?
You can define view for this
view.py
from django.http import HttpResponse
from django.shortcuts import get_object_or_404
from django.contrib.auth.decorators import login_required
from appname.models import Person
#login_required
def show_photo(request):
person = get_object_or_404(Person, user=request.user)
response = HttpResponse(person.photo.read(), content_type="image/jpg")
response['Content-Disposition'] = 'filename=photo.jpg'
return response
urls.py
urlpatterns += [
url(r'^photo/$', show_photo),
]
Users will only see their photo.

Categories

Resources