Search code examples
djangodjango-formsdrybusiness-logicdjango-managers

Django Managers, ModelForms and some confusion


I was planning to write a basic clock-in/clock-out app for worktime tracking using Django. Right now my code works (start/stop a shift or do pauses), but I don't use any ModelForms or ModelManagers. All business code is inside my views and a services.py file, this doesn't seem to be the correct way.

I tried to read into ModelManagers and ModelForms, but I think it just confused the hell out of me.

Let's say I have a model WorkTime:

class WorkTime(models.Model):
    employee = models.ForeignKey(User)
    created_at = models.DateTimeField(auto_now_add=True)
    shift_started = models.DateTimeField()
    shift_finished = models.DateTimeField(blank=True, null=True)
    shift_duration = models.DurationField(blank=True, null=True)
    work_reason = models.TextField(null=True)

I now want to have two possibilities:

  • User can start his shift by visiting a specific View (/clock/start/). Only the starting time is added.
  • User visits the stop view (/clock/stop/) to end his current shift, the finishing time added into the corresponding row.
  • User adds his shift manually after it was finished, therefore adding the starting and finishing time.

As my code is right now, it's not a real beauty and definitely not DRY. How can I solve the problem the most clever way, like defining ONE ModelForm that fits all possible cases (Just start a new shift, end a running shift (only by accessing views in the browser) or add a whole shift manually with a HTML-form). Or would I split it up into having a ModelManager that does the shift starting/stopping part and a ModelForm for the manual addition of shits as an actual HTML-form?


Solution

  • Changing shift start and stop time simply by entering some URL via normal GET request is not an good idea. Browsers can cache that requests or fire that requests more than one (there is no pop-up when refreshing page that it can perform operation twice, like on POST requests). So you should better change that to POST requests.

    For all of that views you can of course use one ModelForm:

    class SiftForm(forms.ModelForm):
    
        class Meta:
            model = WorkTime
            fields = ('created_at', 'shift_started', 'shift_finished') # you shouldn't pass user and duration time here
    
    
    
    class ShiftStartView(CreateView):
        model = WorkTime
        form_class = ShiftForm
        success_url = "some_url" # after creating WorkTime, view will redirect to that url. You can (and really should) pass reverse_lazy() here, instead of URL as string, if you don't want to hardcode URL.
    
        def dispatch(self, request, *args, **kwargs):
    
            # if you want to prevent creating 2 started but not stopped WorkTimes for one user, you should put logic preventing it here.
    
            return super(ShiftStartView, self).dispatch(self, request, *args, **kwargs)
    
        def form_valid(self, form):
            worktime = form.save(commit=False)
            worktime.employee = self.request.user
    
            worktime.save()
            return super(ShiftStartView, self).form_valid(form)
    

    That way you can create also forms for creating WorkTime by hand. For creating stop view, you should use UpdateView and get current WorkTime for user in get_object method.