2

I have a class-based view (IndexView at views.py) that shows a table with all the data stored in the database. This view is rendered in index.html using a def get_queryset(self) to obtain all the data. No issues yet.

The difficult part for me is trying to do it using a form, to be able to modify and save the amount column. I have no issues with the POST part, where I use AJAX to save the new values. Where I'm having issues is getting the initial values from the database to populate a Django Form (forms.py)

I tried using initial argument in the definition of the form fields (at forms.py), even overriding __init__ at the form definition, but I don't know how to get "one value". I mean, the closer I've been in my tests was populating a MultipleChoiceField with: forms.ModelMultipleChoiceField(queryset=Item.objects.order_by('code__name')) (achieving a multiple choice field of Item object (1), Item object (2), etc.)

But how do you populate a single IntegerField or CharField with the content of the database to show a form-based table field in the template?

I'm trying first with a single item (to try to understand how to reference one item/value to populate them using the database) before doing it with the whole table:

index.hml:

...
  <form class='my-ajax-form' method='POST' action='.' data-url='{{ request.build_absolute_uri|safe }}'>
    {% csrf_token %}
    {{form.as_table|safe}}
    <button type='submit'>Save changes</button>
  </form>
...

models.py:

class Code(models.Model):
    name = models.CharField(max_length=6)
    description = models.CharField(max_length=100)

    def __str__(self):
        return self.name

class Item(models.Model):
    code = models.ForeignKey(Code, on_delete=models.DO_NOTHING)
    amount = models.IntegerField(default=0)

forms.py:

class ItemForm(forms.Form):
    code = forms.CharField(max_length=6)
    description = forms.CharField(max_length=100)
    amount = forms.IntegerField()

views.py:

class IndexView(generic.ListView, AjaxFormMixin, FormView):
    template_name = 'inventory/index.html'
    context_object_name = 'items_list'
    form_class = ItemForm

    def form_invalid(self, form):
        ...

    def form_valid(self, form):
        ...

    def get_queryset(self):
        ...

I checked these docs & some similar old issues, but I tried some of the suggestions/examples with no success:

I'm using Django 2.0.2

Any help would be appreciated. Thanks in advance.


EDIT: Trying to do something using UpdateView as andi suggests. Added (trying to get one value by id):

class ItemUpdate(UpdateView):
    form_class = JoinForm

    def get_object(self,queryset=None):
        obj = Item.objects.get(id=self.kwargs['2'])
        return obj

And pointed url to path('', views.ItemUpdate.as_view(), name='index') but always get KeyError: '2'

Abel Paz
  • 573
  • 7
  • 20
  • I am not sure using django forms is accurate in your case. For a similar need, I use jquery editinplace (https://github.com/siebertm/jquery-edit-in-place): it's simple and efficient. – albar Mar 13 '18 at 09:26

3 Answers3

1

what about using generic from django.views.generic.edit import UpdateView straight on model?

https://docs.djangoproject.com/en/2.0/ref/class-based-views/generic-editing/#updateview

views.py:

from django.forms import ModelForm, CharField, Textarea
from django.urls import reverse_lazy
from django.views.generic import UpdateView

from demo.models import Item


class ItemForm(ModelForm):
    description = CharField(widget=Textarea)

    class Meta:
        model = Item
        fields = ['code', 'amount']

    def save(self, commit=True):
        item = super(ItemForm, self).save(commit=commit)
        item.code.description = self.cleaned_data['description']
        item.code.save()

    def get_initial_for_field(self, field, field_name):
        if field_name == 'description':
            return self.instance.code.description
        else:
            return super(ItemForm, self).get_initial_for_field(field, field_name)


class ItemUpdateView(UpdateView):

    form_class = ItemForm
    model = Item

    def get_success_url(self):
        return reverse_lazy('item-detail', kwargs={'pk': 1})

urls.py

from django.conf.urls import url
from django.contrib import admin
from django.urls import path


from demo.views import ItemUpdateView

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    path(r'items/<int:pk>/', ItemUpdateView.as_view(), name='item-detail')
]

/templates/demo/item_form.html

<form action="" method="post">{% csrf_token %}
    {{ form.as_p }}
    <input type="submit" value="Update" />
</form>

If something regarding settings or config is unclear please refer to the repo with demo: https://github.com/andilabs/cbv

UPDATE

added modelformset producing what you need in ListView

from django.forms import modelformset_factory


class ItemListView(ListView):
    model = Item

    def get_context_data(self, **kwargs):
        data = super(ItemListView, self).get_context_data()
        formset = modelformset_factory(Item, form=ItemForm)()
        data['formset'] = formset
        return data

this just displays data in forms, you have take care of rest.

andilabs
  • 22,159
  • 14
  • 114
  • 151
  • Hi! But that's for create an "edit button" with its own URL or something similar, isn't it? I don't know how I can use UpdateView to populate the initial form. Yesterday I was reading this one: https://simpleisbetterthancomplex.com/series/2017/10/09/a-complete-beginners-guide-to-django-part-6.html – Abel Paz Mar 07 '18 at 22:01
  • Just give it a try. Copy example code from tutorial and see how it works. Form for that view will be generated automatically on the go. – andilabs Mar 07 '18 at 22:03
  • I tried to do it using UpdateView, but I think I'm not doing it the right way... (added it at the end of my post) (ref: https://chriskief.com/2015/01/19/create-or-update-with-a-django-modelform/ ) – Abel Paz Mar 07 '18 at 23:16
  • @AbelPaz provide there all snippets of modelform too. – andilabs Mar 07 '18 at 23:45
  • Do you mean my forms.py? It's in my post – Abel Paz Mar 07 '18 at 23:58
  • @AbelPaz I fully updated it to the working version. Also added github repo url – andilabs Mar 13 '18 at 15:12
  • Thanks andilabs. Ok, this is using items/ as the URL. I'll check your Github repo, but I think I miss how to show that ListView (in items/) as a pre-populated form (not as a simple list), and not just one item using the URL items/. – Abel Paz Mar 13 '18 at 23:19
  • @AbelPaz you should easily specialise that code into grids using django `formsets`. https://docs.djangoproject.com/en/2.0/topics/forms/formsets/ – andilabs Mar 13 '18 at 23:26
  • @AbelPaz see updated answer. Hopefully it will help you a lot! – andilabs Mar 14 '18 at 10:18
  • Yeah, looks nice! Thanks. I'll try it in a few hours. Anyway, the "def save..." method won't work with this formset right? I think I should use formset.save() in other place (not sure where right now) >. – Abel Paz Mar 14 '18 at 17:31
  • 1
    the method save() will be actually called for each of the form instance when calling `formset.save()` there is iteration over all forms done and save() is executed. See https://github.com/django/django/blob/master/django/forms/models.py#L655 – andilabs Mar 15 '18 at 15:09
  • 1
    I have no idea where to use formset.save >.< Tried different ways and places, but no success. Anyway, this is beyond my question. I'll keep trying... Thank you so so much @andilabs. – Abel Paz Mar 15 '18 at 19:56
  • I finally posted it as another question... https://stackoverflow.com/questions/49350041/saving-class-based-view-formset-items-with-a-new-virtual-column – Abel Paz Mar 23 '18 at 22:20
0

There are multiple ways of loading data into forms, as far as I know:

  • In views.py

You are using FormView CBV which has a func called get_intial:

    def get_initial(self):
        initial = super().get_initial()
        initial['<form-field>'] = <initial-value>
        return initial
  • In forms.py

You can override the init func:

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['<form-field>'].initial = <initial-value>
Sanchit
  • 326
  • 3
  • 6
0

One thing you can do is iterate through the item list, and set the value attribute of the input tag of each field to form.field_name.value

Febin Stephen
  • 71
  • 1
  • 10