How to override Model defaults method of 3rd party installed app, django? - python

I just installed this django-csvimport package. Now I want to override the default values in the Admin area form. I found the code here, which defines the models, and contains the current default text:
class CSVImport(models.Model):
""" Logging model for importing files """
model_choice = []
model_name = models.CharField(max_length=255, blank=False,
default='csvimport.Item',
help_text='Please specify the app_label.model_name',
choices=get_models())
field_list = models.TextField(blank=True,
help_text='''Enter list of fields in order only if
you dont have a header row with matching field names, eg.
"column1=shared_code,column2=org(Organisation|name)"''')
upload_file = models.FileField(upload_to='csv', storage=fs)
file_name = models.CharField(max_length=255, blank=True)
encoding = models.CharField(max_length=32, blank=True)
upload_method = models.CharField(blank=False, max_length=50,
default='manual', choices=CHOICES)
error_log = models.TextField(help_text='Each line is an import error')
import_date = models.DateField(auto_now=True)
import_user = models.CharField(max_length=255, default='anonymous',
help_text='User id as text', blank=True)
def error_log_html(self):
return re.sub('\n', '<br/>', self.error_log)
error_log_html.allow_tags = True
def __unicode__(self):
return self.upload_file.name
So for example I would like override the model_name field default csvimport.Item with something else. I am a bit at a loss how to override this as I do not have an app folder for csvimport, as its a 3rd part installation. It will be my first time overriding a 3rd party installed app model.
Now that I look into it a bit more, not sure if I should override this model or perhaps better the ModelAdmin class of the admin.py file?
Thanks!

"""Your admin.py"""
from csvimport.models import CSVImport
from csvimport.admin import CSVImportAdmin
class MyCSVImportAdmin(CSVImportAdmin):
"""Override some of the form's field properties:
clean, creation_counter, default_error_messages,
default_validators, disabled, empty_value, empty_values .. etc
"""
def get_form(self, request, obj=None, **kwargs):
form = super(MyCSVImportAdmin, self).get_form(request, obj, **kwargs)
form.base_fields["model_name"].initial = 'What you want'
form.base_fields["model_name"].help_text = 'Please customize the fields however you like'
return form
admin.site.unregister(CSVImport)
admin.site.register(CSVImport, MyCSVImportAdmin)

I saw the whole code and django-csvimport package does not provide you the functionality to override anything from their code so its not possible to override without copying app to your project. Below is an example of another app django-oauth-toolkit which uses a user settings param to provide the functionality of modifications.
USER_SETTINGS = getattr(settings, "OAUTH2_PROVIDER", None)
Now the solution would be only to copy the app and then modify the app for your own usage.

Related

Reserved fields of Django model

I'd like to have a Django model with a reserved field, so that no one can set it directly but its value it's generated at saving time. This is useful for example to generate user tokens and I want to prevent developers to directly set a value for the token key. At the same time I would like to be able to treat that field as I do with others, so using __ for fields lookup in queries, or be able to retrieve tokens as:
token = Token.objects.get(key='c331054c00494f6a22f0ebde7a32bf9d4619b988')
So in my mind doing something like:
Token.key = 'my-token-key'
should fail, and even instantiation should fail:
token = Token(key='my-token-key')
So far I came up with this solution, but I'm a bit concerned my changes could break some Django workflow since I'm not sure what my changes will affect:
import binascii
import datetime
import os
from django.contrib.auth import get_user_model
from django.db import models
class Token(models.Model):
"""
An access token that is associated with a user.
"""
id = models.AutoField(primary_key=True)
# By default `get_attname` returns the field `name`,
# but in my case the attribute name is different
_key = models.CharField(max_length=40, unique=True, name='key', db_column='key')
_key.get_attname = lambda: '_key'
name = models.CharField(max_length=255)
user = models.ForeignKey(get_user_model(), related_name='tokens')
created = models.DateTimeField(auto_now_add=True)
last_access_time = models.DateTimeField(null=True, blank=True)
expires = models.DateField(
null=True,
blank=True,
help_text="Leave empty for non-expiring tokens. "
"Once the token has expired you can not extend its validity.",
)
#property
def key(self):
return self._key
#key.setter
def key(self, value):
raise ValueError("Can not set key directly. It is automatically generated when saving the model.")
def save(self, *args, **kwargs):
if not self._key:
self._key = self._generate_key()
super(Token, self).save(*args, **kwargs)
#staticmethod
def _generate_key():
return binascii.hexlify(os.urandom(20)).decode()
#property
def expired(self):
return bool(self.expires and self.expires < datetime.date.today())
def __str__(self):
return '{} - {}'.format(self.user, self.name)
class Meta:
verbose_name = "User Token"
verbose_name_plural = "User Tokens"
unique_together = (('name', 'user'),)
As you can see I tried overriding the get_attname method of the key field (needed because the field name and the property are the same and it would lead to errors loading forms). This seems to work just fine, but I would like to know if this could lead to problems running queries.
Maybe there is a simpler way to do this but I couldn't find anything better.
P.S.: I'm using python2 with Django 1.11
Thanks a lot to everyone!

django model instance method not being called

I want to update my model upon login (to check the authorizations of a person from an external system).
The code of my model looks as follow:
import json
from django.contrib.auth.models import AbstractUser
from django.contrib.auth.signals import user_logged_in
from django.db import models
class Person(AbstractUser):
is_dean = models.BooleanField(null=False, blank=False, default=False)
is_institute_manager = models.BooleanField(null=False, blank=False, default=False)
managed_institutes = models.TextField(blank=True, null=True, default=None)
def get_managed_institutes(self):
return json.loads(self.managed_institutes)
def set_managed_institutes(self, value):
self.managed_institutes = json.dumps(value)
# Signals processing
def check_authorizations(sender, user, request, **kwargs):
...
# check if the user is dean
is_dean = False
# logic to check if the user is dean...
user.is_dean = is_dean
# Check if the user manages institutes
is_institute_manager = False
managed_institutes = list()
# Logic to check if the user is managing institutes ...
user.is_institute_manager = is_institute_manager
user.set_managed_institutes = managed_institutes
user.save()
user_logged_in.connect(check_authorizations)
Surprisingly, the boolean flags get set correctly, but the method set_managed_institute never gets called...
I am quite convinced this a trivial mistake from my end, but I can't figure it out.
That is not how you call methods in Python. You need to do so explicitly:
user.set_managed_institutes(managed_institutes)
Or did you mean to define a property?
#property
def managed_institutes(self):
return json.loads(self._managed_institutes)
#managed_institutes.setter
def managed_institutes(self, value):
self._managed_institutes = json.dumps(value)
But also note, you probably want to use a JsonField anyway. If you're using PostgreSQL, there is one defined in Django directly; otherwise there are several third-party libraries that take care of serializing and deserializing your data on load/save.

AttributeError: module 'django.db.models' has no attribute 'get_models'

Why am I getting an issue when calling the get_model() function? Here is what I am trying to do:
#classmethod
def get_content_models(cls):
"""
Return all Package subclasses.
"""
is_content_model = lambda m: m is not Package and issubclass(m, Package)
return list(filter(is_content_model, models.get_models()))
This used to work before, but now after updating to the new Django, it's throwing an error. How can this be resolved?
UPDATE
Below is my model
from django.db import models
class Package(BasePackage):
"""
A package in the package tree. This is the base class that custom content types
need to subclass.
"""
parent = models.ForeignKey("Package", blank=True, null=True, related_name="children", on_delete=models.CASCADE)
titles = models.CharField(editable=False, max_length=1000, null=True)
content_model = models.CharField(editable=False, max_length=50, null=True)
in_menus = MenusField(_("Show in menus"), blank=True, null=True)
login_required = models.BooleanField(_("Login required"), default=False,
help_text=_("If checked, only logged in users can view this Package"))
itinerary = models.ManyToManyField('ItineraryItem', through="PackageItinerary")
def __str__(self):
return self.title
def save(self, *args, **kwargs):
"""
Create the titles field using the titles up the parent chain
and set the initial value for ordering.
"""
if self.id is None:
self.content_model = self._meta.object_name.lower()
self.titles = self.title
super(Package, self).save(*args, **kwargs)
#classmethod
def get_content_models(cls):
"""
Return all Package subclasses.
"""
is_content_model = lambda m: m is not Package and issubclass(m, Package)
return list(filter(is_content_model, models.get_models()))
def get_content_model(self):
"""
Provies a generic method of retrieving the instance of the custom
content type's model for this Package.
"""
return getattr(self, self.content_model, None)
It is an AttributeError owing to the fact that models.get_model() was removed in Dango 1.9.
You are supposed to use dango.apps.apps.get_model().
Some discussion is here and here
Here is how you use it now.
from django.apps import apps
MyModel = apps.get_model('app name where the model is','name of the model you want to get from that app')
# Do your logic here with MyModel
However, if all you want is to get model, why not import it straight away? How you are using the code downstream? Please note that due to change (from 1.9 onwards) the properties of function might have changed. Thus you may want to consider latest module and functions to achieve your results (that you previously used to get). This means more work for you to come in sync with later versions of Django but you might run into problems anyways due to the change in get_model.
In summary, see what the code is doing and adapt to newer versions of Django.
I am not sure if I helped you or confused you. Sorry if I did the later.
Best wishes.
from django.apps import apps
ModelClass = apps.get_model('app_name.ModelClass')
You can now instatiate this class
mc = ModelClass()
Doc here

Validating upload file type in Django

I have a Post model with a filefield which is used to upload files. How can I validate the file type (pdf for now, or any other types if I change to later). Preferably i'd like to validate the content, but if not I guess suffix would do too. I tried to look up online but most of the solutions I found are from way back and as the Django document get updated they don't work any more. Please if anyone can help. Thanks.
class Post(models.Model):
author = models.ForeignKey('auth.User',default='')
title = models.CharField(max_length=200)
text = models.TextField()
PDF = models.FileField(null=True, blank=True)
created_date = models.DateTimeField(
default=timezone.now)
published_date = models.DateTimeField(
blank=True, null=True)
def publish(self):
self.published_date = timezone.now()
self.save()
def __str__(self):
return self.title
With Django 1.11 you can use FileExtensionValidator. With earlier versions, or for extra validation, you can build your own validator based on it. And you should probably create a validator either way because of this warning:
Don’t rely on validation of the file extension to determine a file’s type. Files can be renamed to have any extension no matter what data they contain.
Here's a sample code with the existing validator:
from django.core.validators import FileExtensionValidator
class Post(models.Model):
PDF = models.FileField(null=True, blank=True, validators=[FileExtensionValidator(['pdf'])])
Source code is also available so you can easily create your own:
https://docs.djangoproject.com/en/1.11/_modules/django/core/validators/#FileExtensionValidator
Think of validation in terms of:
Name/extension
Metadata (content type, size)
Actual content (is it really a PNG as the content-type says, or is it a malicious PDF?)
The first two are mostly cosmetic - pretty easy to spoof/fake that information. By adding content validation (via file magic - https://pypi.python.org/pypi/filemagic) you add a little bit of additional protection
Here is a good, related answer: Django: Validate file type of uploaded file It may be old, but the core idea should be easily adapted.
Firstly, I'd advise you change 'PDF' to 'pdf', then
to validate in older versions of Django, you could do this
forms.py
class PostForm(forms.ModelForm):
# fields here
class Meta:
model = Post
fields = ["title", "text", "pdf"]
def clean(self):
cd = self.cleaned_data
pdf = cd.get('pdf', None)
if pdf is not None:
main, sub = pdf.content_type.split('/')
# main here would most likely be application, as pdf mime type is application/pdf,
# but I'd like to be on a safer side should in case it returns octet-stream/pdf
if not (main in ["application", "octet-stream"] and sub == "pdf"):
raise forms.ValidationError(u'Please use a PDF file')
return cd
Here is a simple example for a form with file type validation based on Django 1.11 FileExtensionValidator
class ImageForm(ModelForm):
ALLOWED_TYPES = ['jpg', 'jpeg', 'png', 'gif']
class Meta:
model = Profile
fields = ['image', ]
def clean_avatar(self):
image = self.cleaned_data.get('image', None)
if not avatar:
raise forms.ValidationError('Missing image file')
try:
extension = os.path.splitext(image.name)[1][1:].lower()
if extension in self.ALLOWED_TYPES:
return avatar
else:
raise forms.ValidationError('File types is not allowed')
except Exception as e:
raise forms.ValidationError('Can not identify file type')

In django, how can I retrieve a value from db into a custom field template?

I am using a custom class on my model to provide image uploading, through an app called django-filebrowser.
# myapp/models.py
class Book(models.Model):
name = models.CharField(max_length=30)
image = FileBrowseField("Image", max_length=200, blank=True, null=True)
...
The model uses filebrowser's custom field "FileBrowserField", which adds a link to a separate upload page (http://site/admin/filebrowser/browse/?ot=desc&o=date). What I'd like to do is to tweak the custom form's template to add a "dir" parameter, like so: (http://site/admin/filebrowser/browse/?ot=desc&o=date&dir=book1). book1, in this case, would be retrieved from the "name" CharField of this Book.
I know that the template that I want to modify is rendered by filebrowser's fields.py, and there is a variable that sets the "dir" parameter, but I don't know how to fetch the string value from my own model to fields.py so I can set this variable. Does anyone have any suggestions?
Found a solution elsewhere, so I thought I'd share it:
# models.py
class Book(models.Model):
name = models.CharField(max_length=30)
image = FileBrowseField("Image", max_length=200, blank=True, null=True)
...
def __init__(self, *args, **kargs):
super(Property, self).__init__(*args, **kargs)
self._meta.get_field_by_name("image")[0].directory = self.name

Categories

Resources