Can I add a custom restriction for the folder contents in Plone 4.1. Eg. Restrict the folder to contain only files with extensions like *.doc, *.pdf
I am aware of the general restrictions like file/ folder/ page / image which is available in Plone
Not without additional development; you'd have to extend the File type with a validator to restrict the mime types allowed.
Without going into the full detail (try for yourself and ask more questions here on SO if you get stuck), here are the various moving parts I'd implement if I were faced with this problem:
Create a new IValidator class to check for allowed content types:
from zope.interface import implements
from Products.validation.interfaces.IValidator import IValidator
class LocalContentTypesValidator(object):
implements(IValidator)
def __init__(self, name, title='', description='')
self.name = name
self.title = title or name
self.description = description
def __call__(value, *args, **kwargs):
instance = kwargs.get('instance', None)
field = kwargs.get('field', None)
# Get your list of content types from the aq_parent of the instance
# Verify that the value (*usually* a python file object)
# I suspect you have to do some content type sniffing here
# Return True if the content type is allowed, or an error message
Register an instance of your validotor with the register:
from Products.validation.config import validation
validation.register(LocalContentTypesValidator('checkLocalContentTypes'))
Create a new subclass of the ATContentTypes ATFile class, with a copy of the baseclass schema, to add the validator to it's validation chain:
from Products.ATContentTypes.content.file import ATFile, ATFileSchema
Schema = ATFileSchema.schema.copy()
Schema['file'].validators = schema['file'].validators + (
'checkLocalContentTypes',)
class ContentTypeRestrictedFile(ATFile):
schema = Schema
# Everything else you need for a custom Archetype
or just alter the ATFile schema itself if you want this to apply to all File objects in your deployment:
from Products.ATContentTypes.content.file import ATFileSchema
ATFileSchema['file'].validators = ATFileSchema['file'].validators + (
'checkLocalContentTypes',)
Add a field to Folders or a custom sub-class to store a list of locally allowed content types. I'd probably use archetypes.schemaextender for this. There is plenty of documentation on this these days, WebLion has a nice tutorial for example.
You'd have to make a policy decision on how you let people restrict mime-types here of course; wildcarding, free-form text, a vocabulary, etc.
Related
I've been having some trouble implementing Wagtail CMS on my own Django backend. I'm attempting to use the 'headless' version and render content on my own SPA. As a result, I need to create my own EmbedHandlers so that I can generate URL's to documents and images to a private S3 bucket. Unfortunately, though I've registered my own PrivateS3ImageEmbedHandler, Wagtail is still using the default ImageEmbedHandler to convert the html-like bodies to html. Is there a way for me to set it so that Wagtail uses my custom EmbedHandler over the built in default?
Here's my code:
from wagtail.core import blocks, hooks
from messaging.utils import create_presigned_url
class PrivateS3ImageEmbedHandler(EmbedHandler):
identifier = "image"
#staticmethod
def get_model():
return get_user_model()
#classmethod
def get_instance(cls, attrs):
model = cls.get_instance(attrs)
print(model)
return model.objects.get(id=attrs['id'])
#classmethod
def expand_db_attributes(cls, attrs):
image = cls.get_instance(attrs)
print(image)
presigned_url = create_presigned_url('empirehealth-mso', image.file)
print(presigned_url)
return f'<img src="{presigned_url}" alt="it works!"/>'
#hooks.register('register_rich_text_features')
def register_private_images(features):
features.register_embed_type(PrivateS3ImageEmbedHandler)
You need to ensure that your #hooks.register('register_rich_text_features') call happens after the one in the wagtail.images app; this can be done by either putting your app after wagtail.images in INSTALLED_APPS, or by passing an order argument greater than 0:
#hooks.register('register_rich_text_features', order=10)
I am using django-import-export 1.0.1 with admin integration in Django 2.1.1. I have two models
from django.db import models
class Sector(models.Model):
code = models.CharField(max_length=30, primary_key=True)
class Location(models.Model):
code = models.CharField(max_length=30, primary_key=True)
sector = ForeignKey(Sector, on_delete=models.CASCADE, related_name='locations')
and they can be imported/exported just fine using model resources
from import_export import resources
from import_export.fields import Field
from import_export.widgets import ForeignKeyWidget
class SectorResource(resources.ModelResource):
code = Field(attribute='code', column_name='Sector')
class Meta:
model = Sector
import_id_fields = ('code',)
class LocationResource(resources.ModelResource):
code = Field(attribute='code', column_name='Location')
sector = Field(attribute='sector', column_name='Sector',
widget=ForeignKeyWidget(Sector, 'code'))
class Meta:
model = Location
import_id_fields = ('code',)
and import/export actions can be integrated into the admin by
from django.contrib import admin
from import_export.admin import ImportExportModelAdmin
class SectorAdmin(ImportExportModelAdmin):
resource_class = SectorResource
class LocationAdmin(ImportExportModelAdmin):
resource_class = LocationResource
admin.site.register(Sector, SectorAdmin)
admin.site.register(Location, LocationAdmin)
For Reasons™, I would like to change this set-up so that a spreadsheet of Locations which does not contain a Sector column can be imported; the value of sector (for each imported row) should be taken from an extra field on the ImportForm in the admin.
Such a field can indeed be added by overriding import_action on the ModelAdmin as described in Extending the admin import form for django import_export. The next step, to use this value for all imported rows, is missing there, and I have not been able to figure out how to do it.
EDIT(2): Solved through the use of sessions. Having a get_confirm_import_form hook would still really help here, but even better would be having the existing ConfirmImportForm carry across all the submitted fields & values from the initial import form.
EDIT: I'm sorry, I thought I had this nailed, but my own code wasn't working as well as I thought it was. This doesn't solve the problem of passing along the sector form field in the ConfirmImportForm, which is necessary for the import to complete. Currently looking for a solution which doesn't involve pasting the whole of import_action() into an ImportMixin subclass. Having a get_confirm_import_form() hook would help a lot here.
Still working on a solution for myself, and when I have one I'll update this too.
Don't override import_action. It's a big complicated method that you don't want to replicate. More importantly, as I discovered today: there are easier ways of doing this.
First (as you mentioned), make a custom import form for Location that allows the user to choose a Sector:
class LocationImportForm(ImportForm):
sector = forms.ModelChoiceField(required=True, queryset=Sector.objects.all())
In the Resource API, there's a before_import_row() hook that is called once per row. So, implement that in your LocationResource class, and use it to add the Sector column:
def before_import_row(self, row, **kwargs):
sector = self.request.POST.get('sector', None)
if contract:
self.request.session['import_context_sector'] = sector
else:
# if this raises a KeyError, we want to know about it.
# It means that we got to a point of importing data without
# contract context, and we don't want to continue.
try:
sector = self.request.session['import_context_sector']
except KeyError as e:
raise Exception("Sector context failure on row import, " +
f"check resources.py for more info: {e}")
row['sector'] = sector
(Note: This code uses Django sessions to carry the sector value from the import form to the import confirmation screen. If you're not using sessions, you'll need to find another way to do it.)
This is all you need to get the extra data in, and it works for both the dry-run preview and the actual import.
Note that self.request doesn't exist in the default ModelResource - we have to install it by giving LocationResource a custom constructor:
def __init__(self, request=None):
super()
self.request = request
(Don't worry about self.request sticking around. Each LocationResource instance doesn't persist beyond a single request.)
The request isn't usually passed to the ModelResource constructor, so we need to add it to the kwargs dict for that call. Fortunately, Django Import/Export has a dedicated hook for that. Override ImportExportModelAdmin's get_resource_kwargs method in LocationAdmin:
def get_resource_kwargs(self, request, *args, **kwargs):
rk = super().get_resource_kwargs(request, *args, **kwargs)
rk['request'] = request
return rk
And that's all you need.
Context: I am writing an API (using Flask and MongoEngine) with multiple account types, including perhaps buildings. I need the database to hold some temporary accounts until a particular building registers.
This is how I've been referencing just one type of user:
current_holder_of_stuff = ReferenceField(ActiveUser)
I know GenericReferenceField is also an option, but what if I only want to allow two types of references? Is there anything like:
current_holder_of_stuff = ReferenceField(ActiveUser, TempUser)
Muchos thankos!
It may work to create a parent class of type User and then have inherited classes of ActiveUser and TempUser to deal with the various user types. As for the requirement for current_holder_of_stuff to be two possible document types, you cannot use a single reference field. As you've dismissed using GenericReferenceField then one way might be to add a property method and a StringField with options such as this:
import mongoegine as mdb
class User(mdb.Document):
name = mdb.StringField()
meta = {'allow_inheritance': True}
class ActiveUser(User):
activation_date = mdb.DateTimeField()
class TempUser(User):
date_limit = mdb.DateTimeField()
class Building(mdb.Document):
address = mdb.StringField()
class Stuff(mdb.Document):
user = mdb.ReferenceField(User)
building = mdb.ReferenceField(Building)
currently_with = mdb.StringField(options=['user','building'],required=True)
#property
def current_holder_of_stuff(self):
if self.currently_with == "user":
return self.user
else:
return self.building
You can also use mongoengine's signals to perform checks pre-save to ensure there is only a user or building defined.
I was trying to follow official doc of import-export:
https://django-import-export.readthedocs.org/en/latest/import_workflow.html#import-data-method-workflow
But I still do not know how to glue it to my admin assuming that:
I want only subset of fields (I created Resource model with listed fields, but It crashes while importing anyway with: KeyError full stack bellow.
Where - in which method - in my admin class (inheriting of course ImportExportModelAdmin and using defined resource_class) should i place the code responsible for some custom actions I want to happen after validating, that import data are correct but before inserting them into database.
I am not very advanced in Django and will be thankful for some hints.
Example of working implementation will be appreciated, so if you know something similar on github - share.
you can override it as
to create a new instance
def get_instance(self, instance_loader, row):
return False
your custom save
def save_instance(self, instance, real_dry_run):
if not real_dry_run:
try:
obj = YourModel.objects.get(some_val=instance.some_val)
# extra logic if object already exist
except NFCTag.DoesNotExist:
# create new object
obj = YourModel(some_val=instance.some_val)
obj.save()
def before_import(self, dataset, dry_run):
if dataset.headers:
dataset.headers = [str(header).lower().strip() for header in dataset.headers]
# if id column not in headers in your file
if 'id' not in dataset.headers:
dataset.headers.append('id')
[edit: using django-1.1.1]
Hello,
I'm using django classes that store a blob (a string b64 encoded more exactly) corresponding to an icon for the object.
I serialize the objects as json to communicate with the different front-end clients.
I don't want to expose the pure icon blob in the json result (but serve it as image under a certain url), so my icon field is defined as thus :
icon = models.TextField(null=True, serialize=False)
But when time comes to save my object, I lose my icon, because, obviously, the value is not set by the incoming json dictionnary.
I'd like to write written a hack like so :
class MyIconizedClass(models.Model):
def __init__(self, *args, **kwargs):
if self.pk is not None and self._icon is None:
old_self = MyIconizedClass.object.get(pk=self.pk)
self.icon = old.self.icon
Not very satisfied with this because it would query in DB every time, plus, it will recurse indefinitely if the icon is effectively None.
Another way would be to rewrite the deserializer.
Is there any workaround, using django's internal mechanisms?
Try this:
for deserialized_object in serializers.deserialize("json", data):
try:
existing_object = MyIconizedClass.objects.get(pk=deserialized_object.pk)
deserialized_object.icon = existing_object.icon
except MyIconizedClass.DoesNotExist:
pass
deserialized_object.save()
It also queries the database but it won't cause any recursion.
I changed my model so as to be less query-greedy.
class MyIconizedClass(models.Model):
...
...
class IconClass(models.Model):
obj = models.ForeignKey(MyIconizedClass)
blob = models.TextField()
I only query the IconClass table on demand, when working with certain url entrypoints such as
GET /iconized/42/icon
PUT /iconized/42/icon
DELETE /iconized/42/icon
When accessing the object itself, I don't need to know of icons (GET /iconized/42).