As @stefanw says, it's better user experience to check in the form's clean method.
This is enough if you're very sure that there isn't, and never will be, another way to change the value. But since you can rarely be sure of that, if database consistency is important, you can add another check (in addition to the form), one of:
- The easier and database-independent way is in the model's save method as @umnik700 said. Note that this still doesn't prevent other users of the database (another app, or the admin interface) from creating an inconsistent state.
To be 'completely' sure the database is consistent, you can add a database level constraint. E.g. you can create a migration with RunSQL and SQL, something like (not tested):
migrations.RunSQL('ALTER TABLE app_event ADD CONSTRAINT chronology CHECK (start_date > end_date);')
(Not tested). This may be database dependent, which is a downside of course.
In your example, it's probably not worth it (incorrect start/end times just look a bit weird, but affect only the one inconsistent event), and you don't want manual schema changes. But it's useful in cases where consistency is critical.
EDIT: You can also just save the start time and the duration, instead of the start and end times.