Save conflict when submitting forms.

Hi, I’m kinda new to coding. I’m looking for directions.

Issue 1)

I have model formset, displayed like a list, that have 2 fields each that can be changed. On saving/submit, I also trigger some functions that changes values on the page.
More than one person should be able to work with the same fields at once. Let’s say, like Office 365 or Google Docs.
The problem is that every time someone changes something, it’s easy to override each other, if the client don’t have the latest version of the page, it will override all the changes.

So which way should I take for this conflict to be solved/alerted? I’m running Django on Apache so websocket and channels seems like a bit of work. Is it recommended running Django with ASGI?
Should I run Daphne as primary and Apache as reverse proxy?

Could I use Ajax instead and solve everything with only JavaScript?
Maybe a popup “Models for these forms have changed, please refresh the page. Saving now will override the latest version”.

Issue 2)

The forms take kinda long time to load. I loop the formset in the template, around 40 forms.
So I would like to make them load faster if the solution is auto refresh.
Also, refresh the whole page when changing one field will take a lot of time.

Thanks in advance

Concurrent updates fits into the general category of “hard” problems.

First, you need to decide what you want the UX to be. How do you want the page to behave in this situation?

I do have a situation where I have a set of “settings” presented as a div containing a set of radio buttons. It is possible for multiple people to be looking at those settings at the same time. It’s also possible for there to be an external event changing the values underlying those buttons.
And yes, in this case, it’s all tied together with websockets.

If you want the browser to receive a notification of something being changed, the server needs a way to asynchronously contact the browser.

It could be websockets. It could be long-polling (which still implies or strongly suggests asynchronous code on the server). It could be short-polling.

But, regardless of the mechanism on the browser, you will still also need to manage this on the server. There’s always going to be a race condition existing with multiple conditions.

So yes, everything you’ve identified can be done. Whether you should do it is solely up to you.

(Some side notes: I’ve never run Daphne behind Apache, I only use it with nginx. I believe it can be done, but I’ve never done it. My production deployments use nginx as the web server, uwsgi for the Django components, Daphne for the channnels components, and redis for the Channels layer.)

I’m not sure I’m following what you’re saying here. You don’t need to render the individual forms. You can render the formset, and it will render the forms.

But again, as above, it’s up to you. If the notification you receive from the server (via any of the options above) contains enough information to update the specific field, then yes, you can update that field directly. For example, if you have a websocket listener accepting an “update notice” packet, and that packet contains the field name and id that was changed along with the new value, then you can find the corresponding entry in your formset and update it.

No, it’s not trivial, and I’m not aware of anything approaching a plug-n-play solution, but it can be done.

      <tbody class="table-hover">
            {% for field in jesper1 %}
                {{ field.id }}
                {{ field.errors }}

                <tr class="border">

                    <td>{{ field.DELETE }} </td>

                    <td class="border-right">
                        <a href="{% url 'ändra-ett' field.instance.id object.id %}">
                            {{ field.instance.datum|date:'l' }}
                        </a>
                    </td>

                    <td class="border-right">{{ field.referens_tid }}</td>
                    <td class="border-right">{{ field.antal_djur }}</td>
                    <td class="border-right">{{ field.total_tid }}</td>
                    <td class="border-right">{{ field.chaufför }}</td>
                    <td class="border-right">{{ field.lastbil }}</td>
                    <td>
                    {% for plats in platser %}
                        {% if plats.vilket_lass.id == field.instance.id and plats.lossa == False %}
                                <a href="{% url 'Detalj-lass' field.instance.id %}?retur={{ object.id }}">
                                    {{ plats.plats }}
                                </a>
                        {% endif %}
                    {% endfor %}
                    </td>
                </tr>
                {% for hidden in field.hidden_fields %}
                    {{ hidden }}
                {% endfor %}

Views

if self.request.POST:
formset = chaufför_formset(self.request.POST, queryset=Lassen)
form_title = Vilken_titel(self.request.POST)
else:
formset = chaufför_formset(queryset=Lassen)
form_title = Vilken_titel(initial={‘title_’: self.title_})

context[‘jesper1’] = formset

Forms

class Lass_inblick(forms.ModelForm):
class Meta:
model = Lass
fields = [‘chaufför’, ‘referens_tid’, ‘antal_djur’, ‘total_tid’, ‘lastbil’, ‘datum’]

    widgets = {
        'chaufför': forms.Select(
            attrs={'class': 'form-control form-control-sm', 'style': 'font-size:10px;font-weight:bold; '
                                                                     'width:100px; height:25px;'}),

        'lastbil': forms.Select(attrs={'class': 'form-control', 'style': 'width:50px; height:25px; '
                                                                         'font-size:10px;font-weight:bold;'}),

        'referens_tid': forms.TextInput(attrs={'class': 'form-control form-control-sm',
                                               'style': 'width:70px; height:25px; font-size:13px;',
                                               'readonly': 'True'}),

        'antal_djur': forms.TextInput(attrs={'class': 'form-control', 'style': 'width:70px; height:25px; '
                                                                               'font-size:13px;',
                                             'readonly': 'True'}),

        'title': forms.TextInput(attrs={'class': 'form-control', 'style': 'width:100px; height:35px;',
                                        'readonly': 'True'}),

        'total_tid': forms.TextInput(attrs={'class': 'form-control form-control-sm',
                                            'style': 'width:70px; height:25px; font-size:13px;',
                                            'readonly': 'True'}),
        'datum': forms.TextInput(attrs={'class': 'form-control', 'style': 'width:100px; height:35px;',
                                        'readonly': 'True'}),
    }

def __init__(self, *args, **kwargs):
    super(Lass_inblick, self).__init__(*args, **kwargs)
    self.fields['chaufför'].queryset = Chaufförer.objects.all().order_by('namn')
    self.fields['lastbil'].queryset = Lastbilar.objects.all().order_by('lastbilar')

chaufför_formset = modelformset_factory(Lass, extra=0, form=Lass_inblick, can_delete=True)

Big thanks for the answer. I was googling and fell deeper and deeper about this, always nice to know that it’s just keep digging :slightly_smiling_face: