Select a valid choice while selecting Foreignkey in django admin mongodb - django

database in MongoDB (djongo)
I have registered a model in admin.py
admin.site.register(Media)
models.py
class Media(BaseModel):
_id = models.ObjectIdField(primary_key=True)
url = models.URLField()
media_type = models.CharField(max_length=100)
user = models.ForeignKey(User, db_column="user", on_delete=models.CASCADE)
post = models.ForeignKey(Post, db_column="post", on_delete=models.CASCADE)
group = models.ForeignKey(Group, db_column="group", on_delete=models.CASCADE)
class Meta:
db_table = "media"
while changing values using the admin site I got these errors.
can you help me to solve this error?
django==3.0.5
djongo==1.3.4

I noticed that when you try to save an instance of an object with PK an ObjectId that it is transformed into a string and consequently no longer corresponds to an instance of an object, so with the override of the get_form method in POST you can intercept this data and change the string to ObjectId, but as you can see in the [Django documentation][1]:
The QueryDicts at request.POST and request.GET will be immutable when
accessed in a normal request/response cycle.
so you can use the recommendation from the same documentation:
To get a mutable version you need to use QueryDict.copy()
or ... use a little trick, for example, if you need to keep a reference to an object for some reason or leave the object the same:
# remember old state
_mutable = data._mutable
# set to mutable
data._mutable = True
# сhange the values you want
data['param_name'] = 'new value'
# set mutable flag back
data._mutable = _mutable
where data it is your QueryDicts
In this case:
#admin.register(MyMode)
class MyModelAdmin(admin.ModelAdmin):
list_display = ('....')
....
def get_form(self, request, obj, change, **kwargs):
if request.POST:
# remember old state
_mutable = request.POST._mutable
# set to mutable
request.POST._mutable = True
# сhange the values you want
request.POST['user'] = ObjectId(request.POST['user'])
request.POST['post'] = ObjectId(request.POST['post'])
request.POST['group'] = ObjectId(request.POST['group'])
# set mutable flag back
request.POST._mutable = _mutable
return super().get_form(request, obj=obj, change=change, **kwargs)

Related

How to increase the counter on my Foreign Key Model

I have the following doubt:
Let's assume that my Django project has the following models:
class State(models.Model):
initials = models.CharField('Initials', max_length=2, blank = False)
name = models.CharField('State', max_length=50, blank = False)
count = models.IntegerField('Foo Counter', default=0)
....
class Foo(models.Model):
name = models.CharField('Name', max_length=50, blank = False)
state = models.ForeignKey(State, verbose_name='State', related_name='state_fk', on_delete=models.DO_NOTHING),
....
So i have a form to add Foo instances to my db:
class FooForm(forms.ModelForm):
class Meta:
model = Foo
fields = '__all__'
this is the view.py file:
def home(request):
template_name = 'home.html'
form = FooForm(request.POST or None)
if form.is_valid():
salvar = form.save(commit=False)
salvar.save()
return redirect('FooApp:home')
else:
context = {
'form': form
}
return render(request, template_name, context)
I need that, every time the user registers a new 'Foo' the counter of the 'State' chosen by him is increased by 1, i search a lot here and in docs of Django but i could not find a way to do this.
Why do you need count defined as a model field if it's dependent on a database computation, and not something that will be entered from outside?
As mentioned before, you can add logic in the application to update count value from State to self.foo_set.count()
However, I think that it might worth looking into a different approach which would be defining a cached_property on State as it follows:
#cached_property
def count(self):
return self.foo_set.count()
In this way, you'll be able to access State.count wherever you want in the application and get the right value without worrying to keep it updated.
You may not need to keep track of count manually like that. For any instance of State you can always call:
state.foo_set.count()
That will always give you the current count.

Django forms with prefixes not updating on save()

I iterate through a list of Block objects, instantiate a ModelForm for each of them with a mapping dictionary that links a block_type to a ModelForm model, and then append the form to a list which I pass off to a template for display.
for block in blocks:
block_instance = block_map[block.block_type].objects.get(id=block.id)
new_form = block_forms[block.block_type]
new_form_instance = new_form(
request.user,
request.POST or None,
instance=block_instance,
prefix = block.id
)
form_zones.append(new_form_instance)
Later, while checking request.POST I validate each form
if request.POST.get("save_submit"):
for zone_form_check in story_zones:
for block_form_check in zone_form_check:
if block_form_check.is_valid():
print(block_form_check.cleaned_data.get("content"))
saved = block_form_check.save()
print(saved.content)
valid = True
if valid:
return redirect("Editorial:content", content_id=content_id)
cleaned_data.get("content") produces the updated data, but even after calling save() on the valid form, saved.content produces the object's old content attribute. In other words, a valid form is having save() called upon it, but it is not saving.
One of the forms in question (and currently my only one) is:
class Edit_Text_Block_Form(ModelForm):
content = forms.CharField(widget = forms.Textarea(
attrs = {
"class": "full_tinymce"
}),
label = "",
)
class Meta:
model = TextBlock
fields = []
def __init__(self, user, *args, **kwargs):
self.user = user
super(Edit_Text_Block_Form, self).__init__(*args, **kwargs)
The model in question is a TextBlock, which inherits from a Block objets. Both of those are below:
class Block(models.Model):
zone = models.ForeignKey(Zone)
order = models.IntegerField()
weight = models.IntegerField()
block_type = models.CharField(max_length=32, blank=True)
class Meta:
ordering = ['order']
def delete(self, *args, **kwargs):
# Calling custom delete methods of child blocks
child = block_map[self.block_type].objects.get(id=self.id)
if getattr(child, "custom_delete", None):
child.custom_delete()
# Overriding delete to check if there are any other blocks in the zone.
# If not, the zone itself is deleted
zones = Block.objects.filter(zone=self.zone).count()
if zones <= 1:
self.zone.delete()
# Children of Block Object
class TextBlock(Block):
content = models.TextField(blank=True)
Any ideas for why calling saved = block_form_check.save() isn't updating my model?
Thanks!
I think this is because you've effectively excluded all the model fields from the form by setting fields = [] in the form's Meta class. This means that Django no longer relates the manually-defined content field on the form with the one in the model.
Instead, set fields to ['content'], and it should work as expected.
TL;DR form name cannot start with a number as per html4 specs
Try prefix = "block_%s" % block.id

Django Select Drop Down menu Flow

What I'm trying to do is a 2 tier search with drop down menus using Select widget, the results will be a listing of the fields from my Meta.model. the first Tier is a a State listing from State.model. Upon a select it is supposed to list out all of the cities with in the selected state, the problem I'm having (and I think its due to my lack of knowledge) is that the city listing is not filtered but a listing of all cities in my database regardless of state. I'm not sure where or how to pass my variable to be able invoke my .filter() statement.
models.py
class Meta(models.Model):
rcabbr = models.CharField(max_length = 15)
slug = models.SlugField(unique=False)
state = models.ForeignKey('State')
rc_state = models.CharField(max_length = 3)
oerp = models.CharField(max_length=18)
subgrp = models.SlugField()
sonus_pic = models.CharField(max_length=8)
ems = models.CharField(max_length=14)
agc = models.CharField(max_length=14)
def __unicode__(self):
return self.rcabbr
class State(models.Model):
name = models.CharField(max_length=2)
slug = models.SlugField(unique=True)
state_long = models.CharField(max_length=15)
owning_site = models.CharField(max_length=12)
def __unicode__(self):
return self.name
return self.state_long
forms.py
class states(forms.Form):
invent = [(k.name,k.state_long) for k in State.objects.all()]
rclist = forms.ChoiceField(widget=forms.Select, choices=invent)
class rateCenter(forms.Form):
invention = [(k.id,k.rcabbr,k.rc_state) for k in Meta.objects.all()]
rcviews = forms.ChoiceField(widget=forms.Select, choices=invention)
views.py
def StateAll(request):
""" This lists out all of the states within the database that
are served"""
rclist = states()
return render(request, 'statelist.html',{'rclist': rclist})
def RcView(request):
""" this should list all the rateCenters within
the state that was selected in StateAll() """
rclist = request.GET['rclist']
forms = rateCenter()
return render(request, 'rclist.html',{'forms': forms})
Logic tells me I should to do my .filter() statement in the forms.py but unsure how to pass the result from the request.GET in StateAll() view. I do have the debug_toolbar installed so I can see the variable u'rclist' and the value u'LA' (my test state). I had this working 100% using hyperlinks however the size of my test database is miniscule in comparison to what is going to be in the production version and HREF's are just not possible.
my understanding is:
ChainedForeignKey(LinkedModel, LinkedModel.field = "field in first Tier", chained_model_field = "current model_field")
so simple model should I think be something like this?
def State(model.models):
name = models.CharField(max_length=20) #this is the state abbreviation
state_long = models.CharFeild(max_length=20)#this is state long form
def Meta(model.models):
state = models.CharField(max_length=20)
slug = models.SlugField(unique = False) #same values as rcabbr
rcabbr = ChainedForeignKey(State, chained_field = "state_long",
chained_model_field = "slug")
.....
Does that look about right........so the First Field in the drop down should be the State_long, once selected the next should be the slug?. at which time the slug should be passed to my urls and the views for the that final page.
I am going to try this however I'm not 100% sure how to do my views and if I need to do something with forms page or does this cover it? The documentation is not user friendly for someone new to this so any input would be most appreciated!
There are many third party libraries django-smart-selects and dajax come to mind - that will automate this for you along with provide you the necessary javascript to filter the form fields on the fly.
If you are investigating those, here is how you would do it with just the django forms:
class RateCenterForm(forms.Form):
rate_center = forms.ModelChoiceField(queryset=Meta.objects.all())
def __init__(self, *args, **kwargs):
state = kwargs.pop('state')
super(RaterCenterForm, self).__init__(*args, **kwargs)
self.fields['rate_center'].queryset = Meta.objects.filter(state=state)
A ModelChoiceField is a select drop down that takes its values from a model.
Now in your view, you would call it like this:
def show_rate_centers(request):
form = RateCenterForm(state='SomeState')
# .. your normal logic here

django-rest-framework - trying to set required=False flag on nested 1-to-M?

I'm having some issue with django-rest-framework, and nested objects.
I have a Cart object, as well as CartItem, which links back to a Cart:
class Cart(models.Model):
customer = models.ForeignKey(Customer)
date_created = models.DateTimeField(auto_now_add=True)
date_modified = models.DateTimeField(auto_now=True)
class CartItem(models.Model):
cart = models.ForeignKey(Cart, related_name='cartitems')
product = models.ForeignKey(Product, help_text='Product in a cart')
quantity = models.PositiveIntegerField(default=1, help_text='Quantity of this product.')
date_added = models.DateTimeField(auto_now_add=True, help_text='Date that this product was added to the cart.')
I've created serializers for both:
class CartItemSerializer(serializers.ModelSerializer):
product = serializers.HyperlinkedRelatedField(view_name='product-detail')
class Meta:
model = CartItem
class CartSerializer(serializers.ModelSerializer):
customer = serializers.HyperlinkedRelatedField(view_name='customer-detail')
cartitems = CartItemSerializer(required=False)
total_price = serializers.CharField(source='total_price', read_only=True)
shipping_cost = serializers.CharField(source='shipping_cost', read_only=True)
class Meta:
model = Cart
fields = ('id', 'customer', 'date_created', 'date_modified', 'cartitems', 'total_price', 'shipping_cost')
However, whenever I try to POST to create a new cart, I get an error, assumedly when it tries to set the non-existent CartItem:
TypeError at /api/v1/carts/
add() argument after * must be a sequence, not NoneType
However, a Cart isn't required to actually have CartItems.
Is there any way to get DRF to respect the required=False flag I get on Cart.cartitems?
Cheers,
Victor
EDIT:
I took a stab at tracing it through again:
It's calling BaseSerializer.save() in rest_framework/serializers.py with a CartSerializer object.
def save(self, **kwargs):
"""
Save the deserialized object and return it.
"""
if isinstance(self.object, list):
[self.save_object(item, **kwargs) for item in self.object]
if self.object._deleted:
[self.delete_object(item) for item in self.object._deleted]
else:
self.save_object(self.object, **kwargs)
return self.object
It then calls save_object() on the same class:
def save_object(self, obj, **kwargs):
"""
Save the deserialized object and return it.
"""
if getattr(obj, '_nested_forward_relations', None):
# Nested relationships need to be saved before we can save the
# parent instance.
for field_name, sub_object in obj._nested_forward_relations.items():
if sub_object:
self.save_object(sub_object)
setattr(obj, field_name, sub_object)
obj.save(**kwargs)
if getattr(obj, '_m2m_data', None):
for accessor_name, object_list in obj._m2m_data.items():
setattr(obj, accessor_name, object_list)
del(obj._m2m_data)
if getattr(obj, '_related_data', None):
for accessor_name, related in obj._related_data.items():
if isinstance(related, RelationsList):
# Nested reverse fk relationship
for related_item in related:
fk_field = obj._meta.get_field_by_name(accessor_name)[0].field.name
setattr(related_item, fk_field, obj)
self.save_object(related_item)
# Delete any removed objects
if related._deleted:
[self.delete_object(item) for item in related._deleted]
elif isinstance(related, models.Model):
# Nested reverse one-one relationship
fk_field = obj._meta.get_field_by_name(accessor_name)[0].field.name
setattr(related, fk_field, obj)
self.save_object(related)
else:
# Reverse FK or reverse one-one
setattr(obj, accessor_name, related)
del(obj._related_data)
The Cart object has a _related_data field that is set to a dict:
{'cartitems': None}
Hence, on the second-last line, it calls setattr in django/db/models/fields/related.py:
def __set__(self, instance, value):
if instance is None:
raise AttributeError("Manager must be accessed via instance")
manager = self.__get__(instance)
# If the foreign key can support nulls, then completely clear the related set.
# Otherwise, just move the named objects into the set.
if self.related.field.null:
manager.clear()
manager.add(*value)
It's this last liner (manager.add(*value)) that causes the:
TypeError: add() argument after * must be a sequence, not NoneType
Checking the Serializer Relation Docs, first you need to add many=True to your cartitems field.
Unfortunately this is read-only. The docs just say "For read-write relationships, you should use a flat relational style" — you can find a question about that here (although that's only dealing with the 1-1 case).
Current strategies involve making cartitems read-only and then either: doing something post_save, using a second serializer or making a separate request to a separate endpoint to set the related entities. Given that better support for Nested Writes is coming I'd probably be inclined towards a separate request to a separate endpoint for the moment (though that will obviously depend on your constraints).
I hope that helps.
EDIT: (After update to question & discussion in comments).
If you're using a separate endpoint for adding CartItems then making cartitems read-only should eliminate the error.
However (if you're not making it read-only) looking at the DRF code you posted from save_object it occurs that in the related_item in related block you really do need a list. The appropriate dict (fragment) for a Cart with no CartItems is not {'cartitems': None} but rather {'cartitems': []}. — This of course means your required=False flag isn't doing anything. (So perhaps the short answer is "No" — Will now defer to the mailing list discussion

Free-form input for ForeignKey Field on a Django ModelForm

I have two models related by a foreign key:
# models.py
class TestSource(models.Model):
name = models.CharField(max_length=100)
class TestModel(models.Model):
name = models.CharField(max_length=100)
attribution = models.ForeignKey(TestSource, null=True)
By default, a django ModelForm will present this as a <select> with <option>s; however I would prefer that this function as a free form input, <input type="text"/>, and behind the scenes get or create the necessary TestSource object and then relate it to the TestModel object.
I have tried to define a custom ModelForm and Field to accomplish this:
# forms.py
class TestField(forms.TextInput):
def to_python(self, value):
return TestSource.objects.get_or_create(name=value)
class TestForm(ModelForm):
class Meta:
model=TestModel
widgets = {
'attribution' : TestField(attrs={'maxlength':'100'}),
}
Unfortunately, I am getting: invalid literal for int() with base 10: 'test3' when attempting to check is_valid on the submitted form. Where am I going wrong? Is their and easier way to accomplish this?
Something like this should work:
class TestForm(ModelForm):
attribution = forms.CharField(max_length=100)
def save(self, commit=True):
attribution_name = self.cleaned_data['attribution']
attribution = TestSource.objects.get_or_create(name=attribution_name)[0] # returns (instance, <created?-boolean>)
self.instance.attribution = attribution
return super(TestForm, self).save(commit)
class Meta:
model=TestModel
exclude = ('attribution')
There are a few problems here.
Firstly, you have defined a field, not a widget, so you can't use it in the widgets dictionary. You'll need to override the field declaration at the top level of the form.
Secondly get_or_create returns two values: the object retrieved or created, and a boolean to show whether or not it was created. You really just want to return the first of those values from your to_python method.
I'm not sure if either of those caused your actual error though. You need to post the actual traceback for us to be sure.
TestForm.attribution expects int value - key to TestSource model.
Maybe this version of the model will be more convenient for you:
class TestSource(models.Model):
name = models.CharField(max_length=100, primary_key=True)
Taken from:
How to make a modelform editable foreign key field in a django template?
class CompanyForm(forms.ModelForm):
s_address = forms.CharField(label='Address', max_length=500, required=False)
def __init__(self, *args, **kwargs):
super(CompanyForm, self).__init__(*args, **kwargs)
try:
self.fields['s_address'].initial = self.instance.address.address1
except ObjectDoesNotExist:
self.fields['s_address'].initial = 'looks like no instance was passed in'
def save(self, commit=True):
model = super(CompanyForm, self).save(commit=False)
saddr = self.cleaned_data['s_address']
if saddr:
if model.address:
model.address.address1 = saddr
model.address.save()
else:
model.address = Address.objects.create(address1=saddr)
# or you can try to look for appropriate address in Address table first
# try:
# model.address = Address.objects.get(address1=saddr)
# except Address.DoesNotExist:
# model.address = Address.objects.create(address1=saddr)
if commit:
model.save()
return model
class Meta:
exclude = ('address',) # exclude form own address field
This version sets the initial data of the s_address field as the FK from self, during init , that way, if you pass an instance to the form it will load the FK in your char-field - I added a try and except to avoid an ObjectDoesNotExist error so that it worked with or without data being passed to the form.
Although, I would love to know if there is a simpler built in Django override.