1

I have created a ModelForm (Django 1.9.1) for my application settings page. It should work in the following manner:

  1. Settings page should have multiple text fields with the value from database.
  2. If I change any field and then press "Save" button - it should update same fields in DB, not add new.

Model:

class Settings(models.Model):
    pkey_path = models.CharField(max_length=255)

    class Meta:
        db_table = "t_settings"

    def __str__(self):
        return self.id

Form:

class SettingsForm(ModelForm):
    class Meta:
        model = Settings
        fields = ['pkey_path']

View:

def settings_update(request):
    if request.method == "POST":
        form = SettingsForm(request.POST)
        if form.is_valid():
            form.save()
            return render(request, 't_settings/index.html', {'form': form})
    else:
        form = SettingsForm()
    return render(request, 't_settings/index.html', {'form': form})

urls.py:

app_name = 't_settings'
urlpatterns = [
    url(r'^$', views.index, name='index'),
    url(r'^update/', views.settings_update, name='settings-update'),
]

html form:

<form action="{% url 't_settings:settings-update' %}" method="post" class="form-horizontal">
            {% csrf_token %}
          <div class="box-body">
            <div class="form-group">
              <label for="{{ form.pkey_path.id_for_label }}" class="col-sm-3 control-label">test</label>
              <div class="col-sm-8">{{ form.pkey_path.errors }}
                <input type="text" class="form-control"
                       name="{{ form.pkey_path.name }}"
                       id="{{ form.pkey_path.id_for_label }}"
                       placeholder="Path"
                       value="{{ form.pkey_path.value }}">

I tried different approaches using Django docs, but anyway I get:

  1. {{ form.pkey_path.value }} doesn't show the value from database in template
  2. Form itself works, but adds new rows in database, instead of updating the existing ones
DzLL
  • 59
  • 2
  • 7

2 Answers2

0

You could override the model's save method to check if pkey_path already exists and overwrite existing record if so.

from django.core.exceptions import ObjectDoesNotExist

class Settings(models.Model):
    pkey_path = models.CharField(max_length=255)

    class Meta:
        db_table = "t_settings"

    def __str__(self):
        return self.id

    def save(self, *args, **kwargs):
        try:
            # will update existing record
            self.pk = Settings.objects.get(pkey_path=self.pkey_path).pk 
        except ObjectDoesNotExist:
            # if not existing record, will write a new one
            self.pk = None  
        # call the original save method
        super(Settings, self).save(*args,**kwargs)

Note that there will only be an instance associated w/ the form if successfully saved as your view lists now.

If this view is only to update existing records, you could do the following so there is always an instance:

View:

from django.shortcuts import get_object_or_404
from my_app.models import Settings
def settings_update(request, pkey_path=None):
    instance = get_object_or_404(Settings,pkey_path=pkey_path)

    if request.method == "POST":
        form = SettingsForm(request.POST, instance=instance)
        if form.is_valid():
            form.save()
            return render(request, 't_settings/index.html', {'form': form})
    else:
        form = SettingsForm(instance=instance)
    return render(request, 't_settings/index.html', {'form': form})

You must add an entry to urls.py which includes (?P<pkey_path>.+) for this view so that it fetches an existing Settings record. To add a new record, you could write a settings_add view or if no pkey_path supplied to the view make instance=Settings() to instantiate an empty instance of the Settings model.

EDIT #2: Handling a new record vs an existing record requires more than what you have in your view. Here's one way to do that.

In urls.py:

urlpatterns = patterns('',
    url(r'^settings/$',views.settings_update, name='add-settings'),
    url(r'^settings/(?P<pkey_path>.+)/$',views.settings_update, name='update-settings'),
    # ...
)

In views.py:

from django.shortcuts import get_object_or_404
from my_app.models import Settings


def settings_update(request, pkey_path=None):
    if pkey_path: # if supplied via url, find record w/ pkey_path. If pkey_path supplied matches no records, return 404. 
        instance = get_object_or_404(Settings,pkey_path=pkey_path)
    else: # if pkey_path not supplied in url, create empty instance
        instance = Settings() # instantiate new Settings object

    if request.method == "POST": # if post submitted, save if valid
        form = SettingsForm(request.POST, instance=instance)
        if form.is_valid():
            form.save()
            return render(request, 't_settings/index.html', {'form': form})
    else: # no post submitted
        form = SettingsForm(instance=instance)
    return render(request, 't_settings/index.html', {'form': form})
Ian Price
  • 7,416
  • 2
  • 23
  • 34
  • Settings page should be opened by "/settings/ URL. How can I get during page load? – DzLL Jan 17 '16 at 22:42
  • Please don't delete previous comments, they may help future readers. – Ian Price Jan 17 '16 at 23:07
  • So, you want a single view where someone enters pkey_path in your form and creates a new record if that doesn't exist OR overwrites existing record if user enters an existing pkey_path? Not to be rude but it might be good to do some more tutorials on Modelforms and the Django UrlConf, there's some basic practices that may shed light on your question. – Ian Price Jan 17 '16 at 23:11
  • And to clarify, '/settings/' url would, in this case, bring up an empty new form while going to '/settings/some_pkey_path/' will fetch an existing record with the pkey_path supplied in the url. Meanwhile, the overridden save method on the model would take any attempt to save a Odell record and fetch the a model with the pkey_path field if it already exists and write new values to it. However, this is overcomplicating the general process to update or add a record with a modelform – Ian Price Jan 17 '16 at 23:15
  • But if I will have multiple settings in future? URL can be messy... Also, '/settings/' - I mean that user open page with all settings, not some route after POST request. – DzLL Jan 17 '16 at 23:22
  • In that case (all Settings displayed) you'll need to use a formset (a single modelform only shows one instance of a model, while a formset displays multiple instances and allows you save all at once). – Ian Price Jan 17 '16 at 23:42
0

You're not doing anything in your view to either get an existing Settings entry to pass to the form, or tell the form which one to update on save. You need to have some way of identifying the object you want, for a start, which usually means accepting an id or slug in the URL; then you would need to query that object and pass it to the form with the instance parameter, both on initial instantiation and before saving.

Daniel Roseman
  • 588,541
  • 66
  • 880
  • 895