I have these two models:
class Service(MelosModel):
performer = models.ForeignKey(Performer)
event = models.ForeignKey('Event')
composition = models.ForeignKey(Composition)
class Event(MelosModel):
event_type = models.ForeignKey('EventType')
project = models.ForeignKey(Project)
works = models.ManyToManyField(Work)
date_of_event = models.DateTimeField()
location = models.ForeignKey(Address)
Note: A MelosModel is for all intents and purposes the same as models.Model. Also, Composition extends Work.
The trouble is that the list of compositions in the Service admin form needs to be validated against the available Works from its Event. How do you do this?
I read about making a ModelChoiceField from a queryset but that wouldn't help because we don't know what the Event is until the form is submitted. What is the best way to deal with this?
If I understood correctly you could write a clean() method on you Service model class to do the custom validation.
Related
I have a custom contact form for which I create a sent_time field using auto_now_add to save the time when the user had sent the message.
I am able to list all the information on the listing view of the admin panel however when I try to enter a specific message I hit the following error:
'sent_time' cannot be specified for GeneralContact model form as it is a non-editable field
My attempt to make the fields readonly in the ModelAdmin results in the same error
class GeneralContactAdmin(ModelAdmin):
"""
Admin model for general correspondence via
the main contact form on the information page
"""
model = GeneralContact
list_display = GeneralContact.__all__
search_fields = GeneralContact.__all__
readonly_fields = GeneralContact.__all__
ordering = ('-sent_time',)
list_filter = ('sent_time', 'has_response')
Surely it is possible to be displayed only, perhaps I've done something incorrectly in my models?
Here is the base model I use for the contact model
class ContactFormBase(models.Model):
__all__ = (
'sent_time', 'sender_name', 'sender_email',
'sender_message', 'has_response', 'responded_on'
)
sent_time = models.DateTimeField(auto_now_add=True)
sender_name = models.CharField()
sender_email = models.EmailField()
sender_message = models.TextField()
has_response = models.BooleanField(
default=False,
help_text='Select whether this message has been replied to by an admin.',
)
responded_on = models.DateTimeField(blank=True, null=True)
panels = [
FieldRowPanel([
FieldPanel('sender_name'),
FieldPanel('sender_email'),
]),
FieldPanel('sent_time'),
FieldPanel('sender_message'),
FieldRowPanel([
FieldPanel('has_response'),
FieldPanel('responded_on'),
])
]
class Meta:
abstract = True
ordering = ['-sent_time',]
The actual class being used is rather plain, perhaps something needs to be done here to allow display of readonly fields?
class GeneralContact(ContactFormBase, models.Model):
panels = ContactFormBase.panels
class Meta:
verbose_name = 'General Contact Entry'
verbose_name_plural = 'General Contact Entries'
In the list view all the information is able to be displayed. In the editing view, ideally there would be all of the information about the message and sender as readonly fields and an option for the admin to change the has_response value based on whether someone has responded or not.
In what way could I achieve this?
update
After seeing this Q&A I have changed the auto_now_add to use django.utils.timezone.now as the default on the sent_time attribute and life seems better, the error from the start of the question is gone and the edit view loads up entirely. However, now all the fields are editable which is not desirable.
Looking into the ModelAdmin class provided by Wagtail it appears that readonly_fields isn't available and perhaps only a feature of the django admin class of the same name. So I'm not sure what to do here. Wagtails HelpPanel type of output is what I'm looking for, and I had an idea to use that to display the data but I'm not sure what that looks like or even how it'd be done as I'm just learning django and wagtail.
update 2
Attempted to use HelpPanel instead of FieldPanel in order to try display the values but seems as if the HelpPanel doesn't retrieve the value of the attributes. Checking through these docs I see the mention of things like djangos readonly_field is not included which confirms why one of my former attempts didn't work but I did find mention of inspect_view_enabled which displays the values in a read only fashion and after trying it out it looks very much how I was trying to get it, alas, nothing there is editable which makes sense but I am getting closer.
I am wondering if a good solution would be to override the view or template used for GeneralContactAdmin but unsure if that's the right way to go about it just to output some text for one class.
A simpler solution is to keep the inspect view and only add the has_response to the edit view, but two views, one of which would only be a checkbox is not a nice for UX.
Surely there is a better way to solve this?
I have built an Angular form that contains a form array. So the user has the control to add fields or delete fields from the form. What I am trying to understand is how I can design a Django API that can handle the post for this kind of dynamic form?
I do not want the user to create an account on the website in order to place his order. Is that even possible?
You should be more concerned about how to model your data, than you can think about your interface. Here a few options for modeling your data:
Option One is to use regular Django ORM, and in this case you may use the JSONField for any dynamic properties.
Option two is to use any schemaless data model, such as document-based databases(e.g MongoDB).
Here a simple example, on how to use Django's JSONField:
your model:
class Order(models.Model):
customer = models.ForeignKey(User, on_delete=models.CASCADE)
#any additional static fields
properties = JSONField()
your view:
def create_order_view(request):
if request.method == "POST":
#do your validation
Order.objects.create(user=request.user, properties=request.POST["properties"])
return Response(status=200)
this example is totally incomplete as you have to add validation error handling, and it is a better idea to use Django rest-framework for constructing your API.
Finally as I said there many option to model your data, in addition to what I mentioned above there are many other. To decide what model to use, you have to know how your data gonna be consumed, so you can optimze for query time, in addition there are many other factors but this is out of scope of this asnwer.
For me, I used Django-RESTframework to build the api.
The way to achieve this is simple, just create the model and iterate through the items which is the dynamic part, and assign the Foreignkey field to obj.id created. First, I created the main model instance, then created the instances of the child instances. I will use Order and Item to demonstrate the idea, The Item instance will have Foreinkey field to Order model.
In the Item model, add "related_name" argument to the Foreinkey field
order = models.ForeignKey(Order, related_name='items',on_delete=models.CASCADE)
serializers.py
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = Item
fields = [
....your fields...
]
class OrderSerializer(serializers.ModelSerializer):
items = ItemSerializer(many=True)
class Meta:
model = Order
fields = [
'order', ....
]
def create(self, validated_data):
items_data = validated_data.pop("items")
order = Order.objects.create(**validated_data)
order.total_fees = order.delivery_fees
for item in items_data:
i = Item.objects.create(order=order, **item)
return order
I have large table of data (~30 Mb) that I converted into into a model in Django. Now I want to have access to that data through a REST API.
I've successfully installed the Django REST framework, but I'm looking for a way to automatically create a URL for each field in my model. My model has about 100 fields, and each field has about 100,000 entries.
If my model is named Sample,
models.py
class Sample(models.Model):
index = models.IntegerField(primary_key=True)
year = models.IntegerField(blank=True, null=True)
name = models.TextField(blank=True, null=True)
...97 more fields...
then I can access the whole model using Django REST framework like this:
urls.py
class SampleSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Sample
fields = ( **100 fields**)
class SampleViewSet(viewsets.ModelViewSet):
queryset = Sample.objects.all()
serializer_class = SampleSerializer
router = routers.DefaultRouter()
router.register(r'sample', SampleViewSet)
But of course my browser can't load all of that data in a reasonable amount of time. I could manually make a different class and URL for each field, but there must be a better way... I want to be able to go to my_site.com/sample/year (for example) and have it list all of the years in JSON format, or my_site.com/sample/name and list all the names, etc.
Please help me figure out how to do this, thanks!
You might be able to do that using a custom viewset route.
You have this:
class ModelViewSet(ModelViewSet):
#list_route()
def sample_field(self, request):
desired_field = request.data.get('field', None)
if not desired_field:
return response # pseudocode
values = Model.objects.all().values_list(desired_field, flat=True)
# serialize this for returning the response
return Response(json.dumps(values)) # this is an example, you might want to do something mode involved
You will be able to get this from the url:
/api/model/sample_field/?field=foo
This extra method on the viewset will create a new endpoint under the samples endpoint. Since it's a list_route, you can reach it using /sample_field.
So following your code, it would be:
mysite.com/sample/sample_field/?field='year'
for example.
There are many interesting details in your question, but with this sample I think you might able to achieve what you want.
Try to use pagination. You can do it in almost the same way as in you question. Pagination in django lets you divide the results into pages. You don't have to display all the entries in the same page. I think this is the best option for you.
Refer django documentation on pagination:
Pagination in django
I have a model named Domain which looks like this:
class Domain(models.Model):
"""
Model for storing the company domains
"""
user = models.ForeignKey(
User
)
host = models.CharField(
null=False, verbose_name="Host", max_length=128, unique=True
)
I'd like to use Django's generic views for doing CRUD operations on this. There is one field in this model that needs user input but the foreign key field doesn't need any user input. How can I exclude that field from the form that my generic view generates but assign it the value of the current authenticated user.
Thanks.
Have a look at Russel's answer to a similar question on the django-users group earlier this week.
Quoting the answer*:
Forms and Views solve different problems.
The View is solving the problem of "how do I handle this request and
convert it into a response?". The Form is solving the problem of "How
do I convert the POST data in this request into a model object (or a
change to a model object)?".
Very roughly, a view is doing the following:
View gets a request
View works out whether this is a GET or a POST
If its a POST, View asks the Form to turn the Post into a model change
Form returns success or failure
View responds to the success or failure of the Form.
View returns a response.
The functionality of the Form is a complete subset of the
functionality of the View -- and for this reason, it's a completely
interchangable internal component.
Now, in simple situations, it's possible for a View to guess all the
defaults for the form -- all it needs to know is that you're dealing
with a Foo model, and it can construct a default Foo ModelForm.
However, if you have more sophisticated form requirements, you're
going to need a customized Form.
We could have implemented this by exposing all the options of
ModelForm on the View class; but in order to keep everything clean, we
kept the ModelForm isolated, and provided the View with a way to
specify which Form class it's going to use.
So - to cover your use case of excluding fields, you define a
ModelForm that excludes the fields, then let the CreateView know the
form you want to use:
class CampaignForm(forms.ModelForm):
class Meta:
model = Campaign
exclude = ('user', 'name', 'content_inlined')
class CreateCampaignView(CreateView):
form_class = CampaignForm
template_name = "forms/create.html"
I'm guessing when you say "fix a values for a field", you mean setting
the values of user, name and content_inlined before you save the new
Campaign instance; to do this, you need to inject some extra code into
the form processing logic of the form:
class CreateCampaignView(CreateView):
form_class = CampaignForm
template_name = "forms/create.html"
def form_valid(self, form):
form.instance.user = ... (something meaningful.. e.g., self.request.user)
return super(CreateCampaignView, self).form_valid(form)
This overrides the default behavior when the form is valid, and sets
the extra values. The super() implementation of form_valid() will then
save the instance.
For the record, this could also be done by overriding the save()
method on the ModelForm -- however, if you do that, you lose the
request object, which you will need if you're trying to set the
instance values to something that is request-sensitive.
*the original answer set self.object.user instead of form.instance.user. This gives an AttributeError so I have changed it above.
I am not sure if my language clear enough but basically I have this form:
class Paper(models.Model):
number = models.CharField(max_length=12,primary_key=True)
project = models.ForeignKey(Project)
class SomeForm(forms.ModelForm):
class Meta:
model = Paper
fields = ('project', 'number')
and django creates a textfield for me. What I want is a select box with the existing primary keys.
Thanks.
I don't think you want a modelform - that's for creating and updating model instances. It sounds like you just want a form to select IDs. Use a normal form, but with a ModelChoiceField.
class SomeForm(forms.Form):
ids = forms.ModelChoiceField(queryset=Paper.objects.all())
You'll need to give the Paper model a __unicode__ method that returns self.number.