Django - NEVER update a column when saving - python

I am trying to use citus data (https://www.citusdata.com/) with Django.
Most everything is working so far, except trying to save a model that has already been saved:
NotSupportedError: modifying the partition value of rows is not allowed
This is because django always includes every single field in the update SQL, even if that field has not changed.
In Citus, you must pick a field to be your partitioning field, and then you cannot change it. So, when I'm saving an object, it doesn't like that the partition key is in the update statement, even if it didn't change.
I know that you can pass the update_fields keyword arg to the save method, but I'm wondering if I can somehow tell django to NEVER include a field when updating?

Django does not provide this functionality "out of the box". You could override the save method of your class to set all fields other than your partition field as the value for update_fields
def save(self, **kwargs):
kwargs.setdefault('update_fields', ['field1', 'field2'])
return super(Class, self).save(**kwargs)
A more dynamic option, if you do not want to update this method everytime you change the fields of your class, would be to use the Meta API to get all fields of the class and exclude your partition field
def save(self, **kwargs):
kwargs.setdefault(
'update_fields',
[f.name for f in self.__class__._meta.get_fields() if f.name != 'partition_field']
)
return super(Class, self).save(**kwargs)
There are several other methods by which Django will attempt to update your models. Maybe a base class that all your models inherit from that implement these methods would work

Related

How to add additional keyword argument to all Django fields?

The application I am working on requires merging of identical type Django models. These models hold state that can be altered by chronological events, so it is not as straightforward as deep copying one object to the other, as it is not always correct to take the latest value or always copy truthy values for example.
I have written a model merging class to handle this operation, however, I need to be able to describe on a field by field basis whether it should be included in that merge and if it is to be included, how to handle that merge.
I have already tried creating a dictionary to describe this behaviour and pass it into the merger. However, this becomes unwieldy at greater levels of nesting and is very brittle to codebase change.
I have also tried adding a merge method to each individual model, which solved the problem but is highly susceptible to failure if a foreign key relationship that lives on a different model is missed, or the codebase changes.
I have started writing a custom version of every field in Django, as the fields feel like the correct place for the logic to live, but it also feels unwieldy and brittle to have to maintain custom versions of every field.
Is there a way in Django to add an additional keyword argument to the base Field class or perhaps decorate each field without having to subclass them?
Thanks
Just in case this helps anybody else, I have ended up creating a mixin and subclassing each individual field. Below is a cut down example.
from django.db import models
class MappableFieldMixin():
def __init__(self, should_map=True, map_mode=None, *args, **kwargs):
self.should_map = should_map
if should_map and not map_mode:
raise TypeError('Mappable field requires map_mode if should_map set to True')
self.map_mode = map_mode
super().__init__(*args, **kwargs)
def deconstruct(self):
name, path, args, kwargs = super().deconstruct()
kwargs['should_map'] = self.should_map
kwargs['map_mode'] = self.map_mode
return name, path, args, kwargs
class MappableBooleanField(MappableFieldMixin, models.BooleanField):
pass
Usage:
class Membership(models.Model):
is_active = MappableBooleanField(map_mode=MapMode.MAP_ALWAYS, default=True)
You can find further information on creating custom fields in the Django documentation.

Django: overriding insert, update, delete in a model

Sometimes you want to do something when there is an event for model (on create object, on update object, on delete object).
There is a method you can override in Model called save. And it even has a parameter forced_insert which I first thought would always be set to a proper value meaning whether an object will be created or updated. But the parameter is optional and you cannot expect it to be right.
Searching in source code of Model led me to methods _do_update and _do_insert, but the underscore at the beginning tells me that this method is not recommended for use. And it also have a lot of parameters which pollute code when you override it.
The only solution left that I could find is using django.db.models.signals. But I believe they are meant to be used for external purposes like when you want to create a UserProfile on every User create. But I have some internal purposes like updating fields on update. Also using signals makes code look spreaded and harder to understand.
What would be the right way deal with implementing functional on these Model events?
Look at this simplified condition from django base Model _save_table method - decision to update or insert depends on model pk and force_insert value:
def _save_table(self, raw=False, cls=None, force_insert=False,
force_update=False, using=None, update_fields=None):
updated = False
# ...
if pk_set and not force_insert:
updated = self._do_update()
if not updated:
result = self._do_insert()
# ...
return updated
And you can go that same way in your overrided save method if you want entirely custom update/insert operations:
def save(self, force_insert=False, **kwargs):
updated = False
if self.pk and not force_insert:
updated = self.custom_update()
if not updated:
self.custom_insert()
return updated

Django select existing related record, or create new inline

I have a django model 'User' with a foreignkey to a related model 'Group'.
I am using a modelForm to render the form for creating a user, which allows the user to select a group from a dropdown of existing groups.
However, I'd like the option for the user to create a 'new' Group within that form if they don't find one they want in the list.
I know I could do an inline form, but I'm not sure how to accomplish that while retaining the ability to optionally select an existing related record.
Any advice?
After many hours of rearch, I have found a solution.
I tried many things, including overriding the clean() function on my form, however that required removing immutability and was messy to get the validation right.
Ultimately my solution was so sublcasss ModelChoiceField. In the model choice field, override the to_python() method with your logic to create related object if it does not exist. In addition, I passed this field a queryset paramater so that in my form I was able to pass the newly created object only to this form instance, but not show on every users form.
class FlexibleModelChoiceField(ModelChoiceField):
def __init__(self, queryset, *args, **kwargs):
super(FlexibleModelChoiceField, self).__init__(queryset, *args, **kwargs)
self.queryset = queryset
def to_python(self, value):
try:
# Logic to get or create the model instance object
return model_instance_object
except (ValueError, self.queryset.model.DoesNotExist):
raise ValidationError(self.error_messages['invalid_choice'], code='invalid_choice')

Django admin change list view disable sorting for some fields

Is there are way(s) to disable the sorting function for some fields in django admin change list so for those fields, users cannot click the column header to sort the list.
I tried on the following method, but it doesn't work.
https://djangosnippets.org/snippets/2580/
I also tired to override the changelist_view in ModelAdmin but also nothing happen.
def changelist_view(self, request, extra_context=None):
self.ordering_fields = ['id']
return super(MyModelAdmin, self).changelist_view(request, extra_context)
In the above case, I would like to only allow user to sort the list by ID.
Anyone has suggestion? Thanks.
For Django 1.7 (or the version that I last use) do not support such things. One possible dirty work-around could be defining a model class method and using that method instead of model field.
class TestClass(Model):
some_field = (.....)
other_field = (........)
def show_other_field(self):
return self.other_field
class TestClassAdmin(ModelAdmin):
list_display = ("some_field", "show_other_field")
Since show_other_field is a model class method, django do not knows how to sort (or process) the return result of that method.
But as I said, this is a dirty hack that might require more processing (and maybe more database calls) according to use-case than displaying a field of a model.
Extra: If you want to make a model method sortable, you must pass admin_order_field value like:
def show_other_field(self):
return self.other_field
show_other_field.admin_order_field = "other_field"
That will make your model method sortable in admin list_display. But you have to pass a field or relation that is usable in the order_by method of database api.
TestClass.objects.filter(....).order_by(<admin_order_field>)

Check if a form is valid from within a form field [Django]

I have subclassed a text-field form field in Django to create my own custom widget for a field. I was wondering if it's possible to check if all other fields of the form are valid (I want its server side behavior to vary based on the validation of other fields)
See comment
Something like:
class CustomField(TextInput):
def __init__(self, *args, **kwargs):
...
super(CustomField, self).__init__(*args, **kwargs)
input_type = 'hidden'
def value_from_datadict(self, data, files, name):
aws_file_key = data.get(name, None)
_media_bucket = boto.connect_s3(settings.AWS_ACCESS_KEY_ID,
settings.AWS_SECRET_ACCESS_KEY)\
.lookup(settings.AWS_MEDIA_STORAGE_BUCKET_NAME)
try:
key = _media_bucket.get_key(aws_file_key)
except:
print 'Failed to get key.'
key = None
if key and aws_file_key:
fh = tempfile.TemporaryFile()
key.get_contents_to_file(fh)
fh.seek(0)
files = SimpleUploadedFile(key.name, fh.read())
### IF FORM IS VALID DELETE KEY, OTHERWISE, KEEP IT.
if code_to_check_if_valid:
_media_bucket.delete_key(key)
fh.close()
return files
...... etc.
If you want to validate a certain field depending on the values of other fields, you need to to it at the form level and overwrite the field's clean method. Here's the docs on the subject - they are very good.
class CustomForm(forms.Form):
custom_field = CustomField()
def clean(self):
cleaned_data = super(CustomForm, self).clean()
custom_field = cleaned_data.get("custom_field")
...
If you look at the flow of how forms are validated, you will see that the clean method is run if all the other fields validate independently, so at this stage, the form can be considered valid:
These methods are run in the order given above, one field at a time. That is, for each field in the form (in the order they are declared in the form definition), the Field.clean() method (or its override) is run, then clean_<fieldname>(). Finally, once those two methods are run for every field, the Form.clean() method, or its override, is executed.
The final clean method is actually run regardless of if there's an error so you have to iterate through the cleaned_data to make sure there are no errors
The clean() method for the Form class or subclass is always run. If that method raises a ValidationError, cleaned_data will be an empty dictionary.
The previous paragraph means that if you are overriding Form.clean(), you should iterate through self.cleaned_data.items(), possibly considering the _errors dictionary attribute on the form as well. In this way, you will already know which fields have passed their individual validation requirements.
The clean methods for individual fields are called in the same order as the form declaration order or explicitly specified order. [django source code]
Although I wouldn't recommend this approach over using the clean method for multi-field validation, if your custom field is the last field in the order, you can expect self._errors to indicate whether all other fields have passed validation or not. However at this stage, non-field errors won't be available.

Categories