I'm building a factory with factory_boy that generates a django model. I would like to see what arguments the user inputs inline. My factory itself looks like this
class SomeFactory(factory.django.DjangoModelFactory):
name = factory.Sequence(lambda n: 'Instance #{}'.format(n))
some_other_thing = factory.SubFactory(SomeOtherFactory)
class Meta:
model = SomeModel
Now the user could say s = SomeFactory() and it would work fine, but I want to detect if the user input their own argument. For instance, to tell if the user passed in their own name, as in s = SomeFactory(name='Matt')
What I've tried so far is
Writing my own __init__ function in the SomeFactory class
This gets mysteriously overwritten and is neither called when I call s = SomeFactory(), nor when I call s.__init__()
Same goes for overwriting the __new__ method
Overwriting the poorly named _adjust_kwargs
This gives me all fields as kwargs, not just the ones the user defined. For instance, calling s = SomeFactory(name='Matt'), I would get a kwargs dict with keys for name and some_other_thing, which makes it impossible to tell input their own argument or not
Overwriting _create
Still encounter the same problem with overwriting _adjust_kwargs, in that kwargs doesn't contain the original kwargs, but rather all of the arguments
I think a lot of the functionality I'm after is black-boxed inside of factory_boy's StepBuilder (I suspect it's in the instantiate method) but I have no idea how to modify it to do what I want.
Does anyone have any thoughts on how to figure out which kwargs were set originally in the call to s = SomeFactory()? I.e. determine that if I said s = SomeFactory(name='Matt'), that the user manually set the name?
Thanks!
Update: I'm running django version 1.11.2, factory_boy version 2.8.1, and python version 3.5.2
You can override the create method to only get the user kwargs.
A full example would be something like this:
from django.contrib.auth.models import User
import factory
class UserFactory(factory.DjangoModelFactory):
username = factory.Sequence(
lambda n: 'test'
)
email = factory.Sequence(lambda n: 'user{0}#example.com'.format(n))
class Meta:
model = User
#classmethod
def create(cls, **kwargs):
# here you'll only have the kwargs that were entered manually
print(str(kwargs))
return super(UserFactory, cls).create(**kwargs)
So when I call it:
In [2]: UserFactory(username='foobar')
{'username': 'foobar'}
Out[2]: <User: foobar>
If you want to catch kwargs for other build strategies than create, you would also need to do this for the stub and build method.
Related
I'm following along with a Django Rest Framework tutorial (source code here) and I have a few questions about the below code snippet:
class ReviewCreate(generics.CreateAPIView):
serializer_class = ReviewSerializer
permission_classes = [IsAuthenticated]
throttle_classes = [ReviewCreateThrottle]
def get_queryset(self):
return Review.objects.all()
def perform_create(self, serializer):
pk = self.kwargs.get('pk')
watchlist = WatchList.objects.get(pk=pk)
review_user = self.request.user
review_queryset = Review.objects.filter(watchlist=watchlist, review_user=review_user)
if review_queryset.exists():
raise ValidationError("You have already reviewed this movie!")
if watchlist.number_rating == 0:
watchlist.avg_rating = serializer.validated_data['rating']
else:
watchlist.avg_rating = (watchlist.avg_rating + serializer.validated_data['rating'])/2
watchlist.number_rating = watchlist.number_rating + 1
watchlist.save()
serializer.save(watchlist=watchlist, review_user=review_user)
In the class definition, the variable serializer_class is declared; however in the perform_create method, serializer is an argument. Given the differences in naming, how are these two related?
In the method perform_create, self.kwargs is referenced. However, I don't see a kwargs argument passed to any __init__ method or else attached to the class object. How/where is kwargs passed to the class?
In both cases, I can only assume that the inherited class (generics.CreateAPIView) has an __init__ method that assigns a serializer_class variable to serializer. How it "listens" for a child class definition of serializer_class, I have no idea. And as for kwargs, I'm at a loss for how this is passed to the child class w/o explicitly calling defining it in its arguments.
Edit, this question Kwargs in Django does not answer my question-- it just explains what keyword arguments are. I'm not confused about their name, I'm confused by their invisible yet implicit reference in this code.
Answering your first point, we have to note two things:
First, the method perform_create is used in the create method associated to CreateModelMixin (see https://github.com/encode/django-rest-framework/blob/71e6c30034a1dd35a39ca74f86c371713e762c79/rest_framework/mixins.py#L16). The class CreateAPIView inherits from this mixin and also from GenericAPIView(See https://github.com/encode/django-rest-framework/blob/b1004a47334a0dd1929e6d50b8f7ff6badc959f4/rest_framework/generics.py#L184). As you can see, the create method mentioned above uses the class perform_create method and needs a serializer there. Defining perform_create without that argument would lead to an error when creating objects with this method.
Another thing to note is that the serializer used comes from the get_serializer method. Checking the source code for GenericAPIView (https://github.com/encode/django-rest-framework/blob/b1004a47334a0dd1929e6d50b8f7ff6badc959f4/rest_framework/generics.py#L103) we can see that this method calls get_serializer_class which retrieves the serializer defined by serializer_class.
In conclusion, if you don't modify anything else, the serializer that will be passed as a parameter will be an instance of you serializer class defined in serializer_class.
Getting to your second point, if you try to search the parent class of GenericAPIView and follow on searching the base class from which these classes inherit, you will end up finding that the base class is View from django.views.generic. There you will find in the setup method (https://github.com/django/django/blob/27aa7035f57f0db30b6632e4274e18b430906799/django/views/generic/base.py#L124) where the kwargs attribute is initialized. Also you can see in this method's code documentation the following statement:
"""Initialize attributes shared by all view methods."""
Thus in any view we create (if it has View as its base class) we will always be able to manipulate self.request, self.args and self.kwargs. I hope I explained myself clearly!
Let's assume I have a model like this:
class Data(models.Model):
a = models.CharField()
b = models.CharField()
c = models.IntegerField()
I would like to setup a serializer in such a way that it automatically fills in field c and it is not required for a POST. I tried to overwrite the create function of the serializer, but it doesn't work:
class DataSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Data
fields = ('a', 'b')
def create(self, validated_data, **kwargs):
Data.objects.c = 5
return Data.objects.create(**validated_data)
However, if I try this, I end up with an IntegrityError: NOT NULL constraint failed: model_data.c. What is the syntax that I have to use here?
EDIT: Updated formatting.
The reason you're getting the error because field c is not set to null = True - as such an error is raised at the validation stage even before the serializer hits the create method.
Bear in mind that the process goes like this:
Submit serializer data
field-level validation happens - this includes checks for null integrity, min/max length etc and also any custom field validations defined in def validate_<field_name>
object-level validation happens - this calls the def validate method
validated data is passed to the save method, depending on how you designed the serializer - it will save the instance, or route the data to either create or update
All of the info regarding this can be found in Django's and DRF's docs.
A few things to consider:
are you setting a global default for that field? If so, set the default in your models - c = models.IntegerField(default=a_number_or_a_callable_that_returns_an_integer)
do you intend to display the field? If so, include c in your fields and add one more Meta attribute - read_only_fields = ('c',)
If it's neither of the above, you might want to override the validate_c method
Apologies for the poor formatting, typing it on my phone - will update once I get to a computer
In your code Data.objects.c = 5 does nothing.
If you want to set this value yourself use validated_data['c'] = 5 or Data.objects.create(c=5, **validated_data) (just not both at the same time).
Rather than doing this in the serializer, there are hooks in the generic views that allow you to pass values to the serializer. So in your case you might have:
class DataViewSet(ModelViewSet):
# ...
def perform_create(self, serializer):
serializer.save(c=5)
See the "Save and deletion hooks" section here
I have a model with a version number in it. I want it to self-increment when new data is posted with an existing id via TastyPie. I'm currently doing this via the hydrate method, which works as long as two users don't try to update at once:
class MyResource(ModelResource):
...
def hydrate_version(self, bundle):
if 'id' in bundle.data:
target = self._meta.queryset.get(id=int(bundle.data['id']))
bundle.data['version'] = target.version+1
return bundle
I'd like to do this more robustly by using Django's F() expressions, e.g.:
def hydrate_version(self, bundle):
if 'id' in bundle.data:
from django.db.models import F
target = self._meta.queryset.get(id=int(bundle.data['id']))
bundle.data['version'] = F('version')+1
return bundle
However, this gives me an error:
TypeError: int() argument must be a string or a number, not 'ExpressionNode'
Is there a way to more robustly increment the version number with TastyPie?
thanks!
I would override the save() method for your Django Model instead, and perform the update there. That has the added advantage of ensuring the same behavior regardless of an update from tastypie or from the django/python shell.
def save(self, *args, **kwargs):
self.version = F('version') + 1
super(MyModel, self).save(*args, **kwargs)
This has been answered at github here, though I haven't tried this myself yet. To quote from that link:
You're setting bundle.data inside a hydrate method. Usually you modify bundle.obj in hydrate methods and bundle.data in dehydrate methods.
Also, those F objects are meant to be applied to Django model fields.
I think what you want is:
def hydrate_version(self, bundle):
if bundle.obj.id is not None:
from django.db.models import F
bundle.obj.version = F('version')+1
return bundle
In my Django app I have several different forms, which are similar in style. To not repeat myself over and over again, I try to rewrite the default form settings.
As a start I wanted to set some default settings for every form I use in my app and tried to subclass the django.forms.Form:
class DefaultForm(forms.Form):
error_css_class = 'alert'
error_class = DivErrorList
required_css_class = 'required'
label_suffix = ':'
auto_id = True
class TechnicalSurveyForm(DefaultForm):
location = forms.CharField(label='GPS Location')
satellite = forms.ModelChoiceField(queryset=get_satellites(), empty_label=None)
modem_sn = forms.CharField()
In my views.py I would call the Form simply with
tsurvey = TechnicalSurveyForm()
Unfortunately, the settings I set in DefaultForm are not in place (when I use TechnicalSurvey(auto_id = True, error_class = DivErrorList) they are). So, I guess my approach is totally wrong in some way. Can someone please help me out?
I guess the __init__ of forms.Form initializes attributes of a Form. You need to override the __init__ method and change attributes after Django has done its stuff.
EDIT: Indeed, after checking the django source code, you can see that attributes of a form object are initialized in the __init__ function. The method is visible on the github of django.
class DefaultForm(forms.Form):
def __init__(self, *args, **kwargs):
super(forms.Form, self ).__init__(*args, **kwargs)
self.error_css_class = 'alert'
self.error_class = DivErrorList
self.required_css_class = 'required'
self.label_suffix = ':'
self.auto_id = True
For Python beginners
This behavior is totally normal. Every attributes with the same name declared at the class declaration (as in the author example) will be override if it's also defined in the init function. There's a slightly difference between these two types of attributes declaration.
What is the difference between, say, a EmailField and a validator_email? And is it a bad idea to use both?
Or for those who perfer code
import django.db import models
email = models.EmailField()
vs
import django.db import models
email = models.CharField( max_length=75, validators = validate_email )
From the doc it seems like you could also use validators inside forms but if you already specify a validation restriction inside models.py, then you don't need specify again in the forms, right? So it seems better to me to take care of all of the restriction inside models.py.
I suppose the difference is very little, but then you would be violating the DRY principal, which you probably shouldn't do, unless you have a good reason to do it.
If you go to the code base:
#django.db.fields.__init__.py
class EmailField(CharField):
default_validators = [validators.validate_email]
description = _("E-mail address")
def __init__(self, *args, **kwargs):
kwargs['max_length'] = kwargs.get('max_length', 75)
CharField.__init__(self, *args, **kwargs)
def formfield(self, **kwargs):
# As with CharField, this will cause email validation to be performed
# twice.
defaults = {
'form_class': forms.EmailField,
}
defaults.update(kwargs)
return super(EmailField, self).formfield(**defaults)
As you can see, the model inherits from Charfield, so you lose nothing by using emailfield, where appropriate. Furthermore, the default validator is validate_email. Additionally you get the description variable already defined for you. Lastly, on the backend it is already setting max_length for you at '75'. You could of course override this easily enough by defining a max_length in the same way you would when creating a CharField.
You can see formfields() is returning forms.EmailField from django.forms.
Looking at that, you can see:
#django.forms.fields.py
class EmailField(CharField):
default_error_messages = {
'invalid': _(u'Enter a valid e-mail address.'),
}
default_validators = [validators.validate_email]
def clean(self, value):
value = self.to_python(value).strip()
return super(EmailField, self).clean(value)
However, you would lose any default values that using the EmailField might provide, such as the "correct" error message and the custom clean() method.
In the end, while it looks small, actually a good bit of work has already been done for you. So, in general, you shouldn't violate the DRY principal unless you have a good reason to do so.
Edit:
Regarding the second question, you want the form to validate against whatever criteria you are concerned about, so when you call form.is_valid() it returns True / False when it should and generates the appropriate failure message. Otherwise, is_valid() would validate True, and when you model goes to save, it would fail silently, which would be very hard to track down.