I have a class in which I want to override the get_or_create method. Basically if my class doesn't store the answer I want it do some process to get the answer and it's not provided. The method is really a get_or_retrieve method. So here's the class:
class P4User(models.Model):
user = models.CharField(max_length=100, primary_key=True)
fullname = models.CharField(max_length=256)
email = models.EmailField()
access = models.DateField(auto_now_add=True)
update = models.DateField(auto_now_add=True)
#classmethod
def get_or_retrieve(self, username, auto_now_add=False):
try:
return self.get(user=username), False
except self.model.DoesNotExist:
import P4
import datetime
from django.db import connection, transaction, IntegrityError
p4 = P4.P4().connect()
kwargs = p4.run(("user", "-o", username))[0]
p4.disconnect()
params = dict( [(k.lower(),v) for k, v in kwargs.items()])
obj = self.model(**params)
sid = transaction.savepoint()
obj.save(force_insert=True)
transaction.savepoint_commit(sid)
return obj, True
except IntegrityError, e:
transaction.savepoint_rollback(sid)
try:
return self.get(**kwargs), False
except self.model.DoesNotExist:
raise e
def __unicode__(self):
return str(self.user)
Now I completely admit that I have used the db/models/query.py as my starting point. My problem is this line.
obj = self.model(**params)
I am able to get the params but I haven't defined self.model. I don't understand what that needs to be and it's not intuitively obvious what value that should be. Even looking back at the query.py I can't figure this out. Can someone explain this to me? I would really like to understand it and fix my code.
Thanks
get_or_create is a Manager method, that is you access it via model.objects - it is the manager class that has an attribute model. So maybe the easiest thing to do would be to create a custom Manager and put your method there.
However, fixing your code as it stands is easy. self.model is just the classname - that line is simply instantiating the class with the given parameters. So you could just do
obj = P4User(**params)
although this breaks if you subclass the model.
Daniel was right in his suggestion to use a Manager class. Here is what I ended up with.
# Managers
class P4Manager(models.Manager):
def p4_run_command(self, command):
"""Runs a basic perforce command and return the values"""
p4 = P4.P4()
p4.connect()
values = p4.run(command)
p4.disconnect()
return self.__unify_key_values__(values)
def __unify_key_values__(self, args):
"""Unified method to clean up the lack of standard returns from p4 api"""
final = []
for item in args:
params = dict( [(k.lower(),v) for k, v in item.items()])
results = {}
for k, v in params.items():
if k in ['password', ]: continue
if k in ["access", "update"]:
v = datetime.datetime.strptime(v, "%Y/%m/%d %H:%M:%S")
results[k]=v
final.append(results)
return final
def __get_or_retrieve_singleton__(self, **kwargs):
"""This little sucker will retrieve a key if the server doesn't have it.
In short this will go out to a perforce server and attempt to get a
key if it doesn't exist.
"""
assert len(kwargs.keys())==2, \
'get_or_retrieve() must be passed at one keyword argument'
callback = kwargs.pop('callback', None)
try:
return self.get(**kwargs), False
except self.model.DoesNotExist:
params = self.p4_run_command((kwargs.keys()[0], "-o", kwargs.values()))
if callback:
params = callback(*params)
obj = self.model(**params)
sid = transaction.savepoint()
obj.save(force_insert=True)
transaction.savepoint_commit(sid)
return obj, True
except IntegrityError, e:
transaction.savepoint_rollback(sid)
try:
return self.get(**kwargs), False
except self.model.DoesNotExist:
raise e
class P4UserManager(P4Manager):
"""
A Generic User Manager which adds a retrieve functionality
"""
def get_or_retrieve(self, user):
kwargs = { 'callback' : self.__userProcess__ ,
'user': user }
return self.__get_or_retrieve_singleton__(**kwargs)
def __userProcess__(self, *args):
args = args[0]
if not args.has_key('access'):
raise self.model.DoesNotExist()
return args
# Models
class P4User(models.Model):
"""This simply expands out 'p4 users' """
user = models.CharField(max_length=100, primary_key=True)
fullname = models.CharField(max_length=256)
email = models.EmailField()
access = models.DateField(auto_now_add=True)
update = models.DateField(auto_now_add=True)
objects = P4UserManager()
def __unicode__(self):
return str(self.user)
I hope other find this usefull
Use self instead of self.model.
The code you are copying from, is method for class Queryset. There, self.model is the Model whose queryset is intended to be used. Your method is classmethod of a model itself.
Related
http://www.django-rest-framework.org/api-guide/validators/#currentuserdefault
I wants to read default value from userprofile automatically. Right now the offical method support User and DateTime. But I want my customized value. How can I do that?
owner = serializers.HiddenField(
default=serializers.CurrentUserDefault()
)
This is my workaround. Copy the example from source code and place it here.
Hope near future it has friendly solution.
class CurrentBranchDefault:
def set_context(self, serializer_field):
self.user = serializer_field.context['request'].user
self.branch = self.user.userprofile.selected_branch
def __call__(self):
return self.branch
def __repr__(self):
return unicode_to_repr('%s()' % self.__class__.__name__)
class StaffOrderSerializer(serializers.ModelSerializer):
branch = serializers.HiddenField(default=CurrentBranchDefault())
If you want to calculate one hidden field, using other incoming fields in serializer,
than you need to use serializer_field.context['request'].data
This "data" will be validated before "set_context()", so you can use it in safe.
I hope it will help someone else.
class DefineNoteType:
def set_context(self, serializer_field):
# setting field "type", calculated by other serializer fields
data = serializer_field.context['request'].data
subscriber = data.get('subscriber', None)
connection = data.get('connection', None)
if subscriber:
self.type = 'subscriber_type'
elif connection:
self.type = 'connection_type'
else:
raise serializers.ValidationError('Custom error.')
def __call__(self):
return self.type
class NoteSerializer(serializers.ModelSerializer):
type = serializers.HiddenField(default=DefineNoteType())
I create a custom MultilingualCharField and I want order the instances by it, in the right language. I prefer to do so in the model (tell me if this wasn't a good idea), is it possible?
class Myclass(models.Model):
name = MultilingualCharField(max_length=32, unique=True)
...
def __str__(self):
name_traslated={'name_it': self.name_it, 'name_en': self.name_en}
name_verbose=_('name_it')
return name_traslated[name_verbose]
class Meta:
#name_traslated={'name_it': self.name_it, 'name_en': self.name_en}
name_verbose=_('name_it')
ordering = [name_verbose]
#ordering = [name_traslated[name_verbose]]
__str__ is working but ordering is not: it gives TypeError: 'class Meta' got invalid attribute(s): name_verbose
My MultilingualCharField create two columns: name_it and name_en and I want to order the istances on one of these. If you need it here's the code (from Web Development with Django Cookbook):
class MultilingualCharField(models.CharField):
def __init__(self, verbose_name=None, **kwargs):
self._blank = kwargs.get("blank", False)
self._editable = kwargs.get("editable", True)
#super(MultilingualCharField, self).__init__(verbose_name, **kwargs)
super().__init__(verbose_name, **kwargs)
def contribute_to_class(self, cls, name, virtual_only=False):
# generate language specific fields dynamically
if not cls._meta.abstract:
for lang_code, lang_name in settings.LANGUAGES:
if lang_code == settings.LANGUAGE_CODE:
_blank = self._blank
else:
_blank = True
localized_field = models.CharField(string_concat(
self.verbose_name, " (%s)" % lang_code),
name=self.name,
primary_key=self.primary_key,
max_length=self.max_length,
unique=self.unique,
blank=_blank,
null=False,
# we ignore the null argument!
db_index=self.db_index,
rel=self.rel,
default=self.default or "",
editable=self._editable,
serialize=self.serialize,
choices=self.choices,
help_text=self.help_text,
db_column=None,
db_tablespace=self.db_tablespace
)
localized_field.contribute_to_class(cls,
"%s_%s" % (name, lang_code),)
def translated_value(self):
language = get_language()
val = self.__dict__["%s_%s" % (name, language)]
if not val:
val = self.__dict__["%s_%s" % (name, settings.LANGUAGE_CODE)]
return val
setattr(cls, name, property(translated_value))
Thank you
I have a model with a field name. My custom MultilingualCharField create in the database a field for each language (name_en, name_it etc). I don't have a field name in the database, but only in the model.
So, here what I did (I think can be useful even if you don't use a custom field but a field for each language in the model):
In mymodel.py:
class MyClass(models.Model):
...
class Meta:
ordering = [_('name_it')]
This works in form etc but gives an error in admin (TypeError: expected string or bytes-like object), so here's my Admin.py:
class MyClassAdmin(admin.ModelAdmin):
def get_ordering(self, request):
if get_language_from_request(request)=='it':
return ['name_it']
else:
return ['name_en']
If you find an error or a better way please tell me
I have two models like this:
class Sector(models.Model):
name = models.CharField(max_length=100, db_index=True, unique=True) # HERE IF I REMOVE unique=True, it works correctly
class Address(models.Model):
...
sector = models.ForeignKey(Sector, null=True, blank=True)
And a serializer for the Address model:
In the view, I have this:
address_serialized = AddressSerializer(data=request.data)
if address_serialized.is_valid():
address_serialized.save(client=client)
It never gets to the create function. I have a serialized with a create function that looks like this:
class AddressSerializer(serializers.ModelSerializer):
city_gps = CitySerializer(required=False)
sector = SectorSerializer(required=False)
class Meta:
model = Address
fields = (..., "sector")
def create(self, validated_data):
...
sector_dict = validated_data.get("sector", None)
sector = None
if sector_dict and "name" in sector_dict and city_gps:
if Sector.objects.filter(name=sector_dict["name"], city=city_gps).exists():
sector = Sector.objects.get(name=sector_dict["name"], city=city_gps)
# pdb.set_trace()
if "sector" in validated_data:
validated_data.pop("sector")
if "city_gps" in validated_data:
validated_data.pop("city_gps")
address = Address.objects.create(sector=sector, city_gps=city_gps, **validated_data)
return address
The code never touches this function, is_valid() returns False. And the message is
{"sector":{"name":["sector with this name already exists."]}}
I need to be able to create a new address with FK to the already existing sector. How can I achieve that? Any advice will help.
EDIT
The view looks like this:
class ClientProfileAddressCreateView(APIView):
# throttle_scope = '1persecond'
renderer_classes = (JSONRenderer,)
permission_classes = (IsAuthenticated,)
def post(self, request):
try:
client = Client.objects.get(user=request.user)
except ObjectDoesNotExist:
return Response({"error": "A client profile for the logged user does not exit"},
status=status.HTTP_404_NOT_FOUND)
address_serialized = AddressSerializer(data=request.data)
print("address_serialized.is_valid: %s" % address_serialized.is_valid()) # Returns False when unique=True in models
if address_serialized.is_valid():
# print("address_serialized: %s" % address_serialized.data)
address_serialized.save(client=client)
else:
return Response(data=address_serialized.errors, status=status.HTTP_400_BAD_REQUEST)
return Response(data=address_serialized.data, status=status.HTTP_201_CREATED)
This is a known issue with nested serializers and unique constraints.
Really awesome thing to always do is actually print the Serializer - that can give you a lot of extra info.
When you have a json like this:
{
"Sector": {
"name": "Sector XYZ"
},
"address_line_one": “Some Random Address”
}
Django REST framework does not know whether you're creating or getting the Sector object, thus it forces validation on every request.
What you need to do is the following:
class SectorSerializer(serializers.ModelSerializer):
# Your fields.
class Meta:
model = Address
fields = ("Your Fields",)
extra_kwargs = {
'name': {
'validators': [],
}
}
Then to handle validation you would need to redo the create/update part to fit the uniqueness constraint and raise exception/validation error.
I hope this helps.
Helpful links: This SO Answer and Dealing with unique constraints in nested serializers
EDIT :
As per cezar's request: I will add how it might look like to override the create method of the serializer. I have not tried this code, but the logic goes like this.
class SectorSerializer(serializers.ModelSerializer):
# Your fields.
class Meta:
model = Address
fields = ("Your Fields",)
extra_kwargs = {
'name': {
'validators': [],
}
}
def create(self, validated_data):
raise_errors_on_nested_writes('create', self, validated_data)
ModelClass = self.Meta.model
info = model_meta.get_field_info(ModelClass)
many_to_many = {}
for field_name, relation_info in info.relations.items():
if relation_info.to_many and (field_name in validated_data):
many_to_many[field_name] = validated_data.pop(field_name)
# FIELD CHECK
your_field = validated_data.get("your_field","") # or validated_data["your_field"]
try:
YourModel.objects.filter(your_check=your_field).get()
raise ValidationError("Your error")
except YourModel.DoesNotExist:
# if it doesn't exist it means that no model containing that field exists so pass it. You can use YourQuerySet.exists() but then the logic changes
pass
try:
instance = ModelClass.objects.create(**validated_data)
except TypeError:
tb = traceback.format_exc()
msg = (
'Got a `TypeError` when calling `%s.objects.create()`. '
'This may be because you have a writable field on the '
'serializer class that is not a valid argument to '
'`%s.objects.create()`. You may need to make the field '
'read-only, or override the %s.create() method to handle '
'this correctly.\nOriginal exception was:\n %s' %
(
ModelClass.__name__,
ModelClass.__name__,
self.__class__.__name__,
tb
)
)
raise TypeError(msg)
# Save many-to-many relationships after the instance is created.
if many_to_many:
for field_name, value in many_to_many.items():
field = getattr(instance, field_name)
field.set(value)
return instance
I want to be able to return different values from a Queryset (not changing the database) based on a parameter. I'd like to be able to write it into the model so that it is enforced everywhere.
If the user is a certain type of user, I would want a QuerySet field to be blank (or "hidden" or something similar).
Here's a simplified Model:
class SomeDetails(models.Model):
size = models.FloatField()
this_is_okay_to_show = models.TextField()
not_always_ok = models.TextField()
Simplified Queryset:
qsSomeDetails = SomeDetails.objects.all()
I want not_always_ok to either return the text value stored in the database or return an empty string (or 'hidden' or similar).
Template Filter would work, but it really needs to be in the model.
I'm not sure how to pass a parameter through to make it work.
I feel like the answer is right in front of me, but I'm just not seeing it.
define a Manager class for SomeDetails model:
class SomeDetailsManager(models.Manager):
def __getattr__(self, attr, *args):
try:
return getattr(self.__class__, attr, *args)
except AttributeError:
return getattr(self.get_query_set(), attr, *args)
def get_query_set(self):
return self.model.QuerySet(self.model)
And change your SomeDetails like this:
from django.db.models.query import QuerySet
class SomeDetails(models.Model):
size = models.FloatField()
this_is_okay_to_show = models.TextField()
not_always_ok = models.TextField()
objects = SomeDetailsManager()
class QuerySet(QuerySet):
def get_user(self, user, *args, **kwargs):
_objects = self.filter(*args, **kwargs)
for obj in _objects:
if user.username = 'foo': #change with your condition
obj.not_always_ok = ''
return _objects
Now you can use
qsSomeDetails = SomeDetails.objects.filter(pk=1).get_new_objects(request.user)
note:
first done all your filter and use get_new_objects(request.user) as last part.
I have the following problem.
I have a model Towar:
class Towar(models.Model):
nrSeryjny = models.CharField(max_length=100)
opis = models.CharField(max_length=255)
naStanie = models.NullBooleanField(null=True)
def __unicode__(self):
return "%s" % self.opis
def lowerName(self):
return self.__class__.__name__.lower()
def checkState(self):
return self.naStanie
def changeState(self,state):
self.naStanie=state
class Meta:
ordering=['nrSeryjny']
app_label = 'baza'
permissions=(("view_towar","mozna miec podglad dla towar"),)
and another model, Wypozyczenie, which is linked to Towar by a foreign key relationship:
class Wypozyczenie(models.Model):
dataPobrania = models.DateField()
pracownik = models.ForeignKey(User,null=True)
kontrahent = models.ForeignKey(Kontrahenci,null=True)
towar = models.ForeignKey(Towar,null=True)
objects = WypozyczenieManager()
default_objects = models.Manager()
ZwrotyObjects = WypozyczenieZwrotyManager()
def lowerName(self):
return self.__class__.__name__.lower()
def __unicode__(self):
if self.towar == None:
return "Dla:%s -- Kto:%s -- Kiedy:%s -- Co:%s" % (self.kontrahent,self.pracownik,self.dataPobrania,"Brak")
else:
return "Dla:%s -- Kto:%s -- Kiedy:%s -- Co:%s" % (self.kontrahent,self.pracownik,self.dataPobrania,self.towar)
class Meta:
ordering = ['dataPobrania']
app_label = 'baza'
permissions = (("view_wypozyczenie","mozna miec podglad dla wypozyczenie"),)
and a view to add models:
def modelAdd(request,model,modelForm):
mod = model()
if request.user.has_perm('baza.add_%s' % mod.lowerName()):
if request.method == 'POST':
form=modelForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect('/'+ mod.lowerName() + '/')
else:
form = modelForm()
v = RequestContext(request,{'form':form})
return render_to_response('add_form.html',v)
What I want is that when I add Wypozyczenie and save it, then the Towar that is stored by Wypozyczenie changes its naStanie field from True to False.
Greets
If you want to always keep those two in sync you can override Wypozyczenie's save() method.
class Wypozyczenie(models.Model):
...
def save(self):
self.towar.naStanie = False
self.towar.save()
Alternatively, you can also override ModelForm's save() method.
What have you tried?
Can't you just put
myinstance = form.save()
towar = myinstance.towar
towar.naStanie = False
toware.save()
instead of your simple call to form.save() in your view.
You can use signals emitted when saving your Wypozyczenie object. It might be a little "cleaner" than overriding save(), especially when it's useful to re-use the function for other models:
#receiver(models.signals.post_save, sender=Wypozyczenie)
def after_wypozyczenie_save(sender, instance, created, **kwargs):
# `instance` is your saved Wypozyczenie object, you can
# access all it's fields here:
instance.towar.naStanie = False
instance.towar.save()
# It's also possible to do different action on the first save
# and on subsequent updates:
#
# if created:
# ...
# else:
# ...
There are other signals sent before saving or on deletion. Django documentation on signals is quite helpful here.