Creating an Inclusive Django Community with Kenya Phelps
Published July 15, 2026
This video features Andrej Baranovskij at DjangoCon US 2022 in San Diego, California, USA.
Django HTML templates help to implement UI code in a consistent manner. With stateful session management, security, and ease of data transfer to the UI layer. However, there are cases, when you would want a partial request, instead of a full request reloading the page. This can be achieved with HTMX and Alpine.js integrated into Django HTML templates. This session will present a practical CRUD app. With Tailwind CSS we will make it look modern and usable.
This talk was presented at: https://2022.djangocon.us/talks/modern-apps-with-django-htmx-tailwind-js/
LINKS:
Follow Andrej Baranovskij 👇
On Twitter: https://twitter.com/andrejusb
Follow DjangCon US 👇
https://twitter.com/djangocon
Follow DEFNA 👇
https://twitter.com/defnado
https://www.defna.org/
Andrej Baranovskij shows how to build a responsive employee-management interface with Django, HTMX, Alpine.js, Tailwind CSS, Flowbite, and django-widget-tweaks. Django models and model forms provide fields, relationships, constraints, widgets, and validation, while HTMX sends partial GET and POST requests so only the form or table is replaced instead of reloading the page. Alpine.js controls local state such as showing the edit form and disabling other edit buttons, and HTMX events refresh the table after a successful save. The example also demonstrates field-level and custom validation errors, success messages, and salary-range and uniqueness checks.
Summarised automatically from the transcript.
Automatically transcribed, so expect mistakes in names and technical terms.
Hello, my name is Andrey Barnowski and I'm very happy to present my session on this Django conference. The session will be about how to build modern web applications with Django. uh using um alpinejs uh htmx and uh tailwind uh my background uh uh I'm uh working quite a lot with uh Python uh machine learning and in the past I was uh spent many years working with uh Java Enterprise. uh building uh web um uh enterprise applications but uh for the past few years I um busy with uh machine learning and implementing uh different enterprise um
use cases in uh machine learning field and uh to build UI for machine learning I'm using uh Django uh because uh I found it quite uh uh uh good uh uh in because you can quite easily define uh the model and uh uh models, uh model forms, uh views and uh then you render uh UI uh data uh like a table structures forms out of uh out of this um uh definitions that you created in the jack jungle backend and um This helps to create clean UIs and it helps
to inform to enforce correct data uh to uh when uh user is saving data and so on. And I decided to uh look into HTMX because HTMX helps to Execute partial requests uh with Django without reloading the whole page when user submits data, but uh you execute just a partial request with HTMix. Yeah, so let's uh first of all probably first thing first, let's look into the uh application that I'll be talking today about, and this application implements uh table And this edit button for each row, so we can click uh for example edit
for for the first record and we get uh edit for editable form below the table. Right and uh What you can do here you can change any value for example 2 uh press save and you see that data is saved successfully And it's uh instantly updated on a table above, right? So as expected. And uh as soon as you edit any uh record you can see all the other buttons uh disabled so you can edit records just one record at a time you press close um edit functionalities enabled you can let's open The last record for Steven, and if we try to uh
for example, I'll try to put some uh email which is not uh unique, for example Let's uh use uh this value and try to save it and to get back validation error from Django because these uh unique constraint in Django model which checks uh that email field uh value must be unique for all the users so there is validation as well Okay, so the this is the application and my idea was to have something uh simple use case but the same practical with uh way you would be able to update data and we could uh see how validation works, how we can return validation errors back. Then we have multiple regions on this page
like table and also edit form And when we successfully edit data in form we update another region and for that we're using HTMX events which is returned back with on successful save and um then HTMX library is able to uh get uh uh fresh data for for the for the table and so on. So these there are quite of uh those interactivity use cases here Uh which uh probably you w would be useful for your own applications uh uh in terms of practicality. Alright, so if you switch to the application, so let's see what kind of template HTML templates we have here. So here's the base template and in the base template we import
tailbind CSS And by the way, we're using FlowBite library to implement all the UI for the table, for the fields and for so on, for other other elements like buttons for example. FlowByth is open source library. It comes also with uh uh JavaScript support to implement interactivity, but we are not using any complex components from FlowBite here. So it's enough uh just to include the tailwind and uh that's all then uh flow by styling would work. We're using Alpine GS uh to implement interactivity, uh like to disable the button or hide the edit form and so on so for that we include alpinejs and htmx is the main library which helps to implement um a partial request
get back response and display this response uh in in in the parts of the page so we include HTMX for that. Okay, then we Have index page which extends from base and have we include some static div with uh with text just to demonstrate that when we are running HTMX requests we are not reloading entire page uh portions of the page they stay without any any any reload We include employees HTMX here inside the index and employees HTMX comes with two blocks. First block is table, second one is the form And we can we we are using Alpine GS attributes directly on divs.
With x show attribute we refer to the show edit form variable by default it's false. This means this div is hidden and when we are clicking edit inside the table then this variable is set to true and then div becomes visible. We hit cancel button inside edit form, then we set value false to the show edit form variable and this variable becomes uh it gets value false and uh the block edit block becomes uh hidden so we can we can check that and for that let's open View table and edit form. So in the view table, this is the
edit button, and uh we see uh this Alpine. js on click event listener it and says show edit form equal true this means when we hit the button then show edit form becomes true and edit block is visible and we are also using disabled We are overriding disabled property for the button also using Alpine GS and uh This means that when variable is true, then all edit buttons are disabled. And later, when in edit form we'll uh set this variable to false, then buttons will be enabled and user could uh hit edit form for another record to to edit the data
so this is uh over here we can go to the to the bottom this uh This is a closed button and or cancel button and we set show edit form equal false. This means the form becomes hidden and edit buttons become editable. Okay, so that's about the structure of the UI templates and now uh let's look into the how the models are defined for that application. So the main model is employees model. So table and edit form are rendered out of this model. There is a set of fields defined for the model, like a last name, email, higher date, and so on.
All the fields are assigned with attributes like a max length, primary key um max digits uh decimal places and so on right so Also we have um ordering uh is being specified for for this model and there are a couple of constraints uh like uh check constraint. uh it checks if uh uh salary it's it's enforcing the rule that salary must be positive and there's a unique constraint uh it checks uh that the email value must be uh unique for all the records uh for all the employees. There are a couple of foreign keys defined, one for manager ID, second for department ID, and also there is a foreign
key. for job ID for manager ID foreign key it points to itself to employees model because uh managers are all also the same employees right And there are two other classes defined, one for department model and another one for job model. So those models are used to implement foreign key relationship for departments and for jobs. Right, so this is the model definition, and uh as soon as you define the models in Django application, then you need to uh run uh Django migrate utility. and construct uh a local database structure uh
which would correspond to the uh model class as as you defined to all the fields uh constraints and attributes. And and then uh out of the box you could fetch data from that database and you could uh push data as soon as data is updated and so on. Right, so we've got models. Next uh uh we also discussed models, we discussed uh check constraints, um and now let's look into the forms. So, since we have a model, we can define Django model form on top of the model and we can specify in the model form which fields we want to display on UI.
And also we dispate some sort of widget configuration here for the higher date. We specify that higher date field is a date input field. And later on UI when form will be rendered then like a calendar component will be automatically attached to the higher date fields which is created comes out of the box. Alright, and uh another huge advantage of uh model forms that you can do uh validation right away uh right here in in in inside the model form. And you write a method called clean, then field name. So clean email, clean salary and so on. So when you uh
hit save And all those methods will be executed automatically for you. You need you don't need to call uh anything uh by yourself. All the methods will be executed and you can enforce rules inside those methods. If some of the rules will fall will fail, then the transaction will stop. And validation text will be reported back to the caller and it will be propagated to the UI and reported back to the user. So in clean email, for example, we check that for the email format. In clean salary, it's a bit more interesting here what we have over here we get current job ID.
for the current employee that we are trying to update data and we get uh salary value and later uh we could we get uh through the foreign key relationship we get uh minimum and uh maximum salary for this employee that we are current currently working with and we can check if the salary that we are trying to save if the salary is in range between minimum and maximum as it is defined in the foreign key relationship If the new value doesn't fall into this uh interval then we report back the validation error. So this is how it works. Okay, uh this is um model form.
Next uh let's take a look into the views Or URL URLs. So under URLs we have uh one root URL which points to the index Then there are two URLs that work with employees data. First one it returns list of employees and the second one is responsible to handle post request. when we actually when we actually executing post requests and sending submitting data to update a specific employee Also, it can work uh to get employee because uh if when we pass primary key uh to this uh request, we could get sp uh information about specific employee and
render uh data for of that employee inform. And I'm using Django Browser Reload plugin to be able to refresh application automatically uh when um uh f when when you when when you you when you do any change in quality when you click save then uh The application is reloaded in Google Chrome automatically. In Safari, I think it doesn't work this way, but in Google Chrome it works. It helps to save lots of time and uh improve uh developer experience. Okay, now let's look into the views. Views. Let's uh look into the views
And let's see how they are implemented. So first this index view. Uh it returns index. html as a main uh page of this application. And uh in the get methods of this class we are uh fetching all the employees and uh list of employees included into the uh context and return back uh uh with jungle render function and we're also returning the template name. So what this means uh if you go back to the employees HTMX uh this means we'll we are bringing bringing the content for this uh first uh view which renders the table uh view table this one
And over here we got the loop uh for employee in employees. We are uh basically uh drawing the records uh for employees data. So This employees variable is coming through the context that is defined here in the view. Right, and yeah, another thing is um So that's for the main page and uh later we are using um uh employee table view if you look into the URLs. Uh yeah, we're using it also to bring back the list of employees uh through through this one uh and is being used when we are doing uh refresh uh
when event uh is received um uh from the edit form and we want to refresh the table then we are using uh this um uh this view and um it's basically similar similar thing uh the only difference is that We are not returning the whole index page. We are returning only view table HTMX template which contains only the table. And because uh when something changed uh inside the uh edit form, then we don't need to um reload the whole page we just uh want to redraw the table and we're returning the template with the table only and also all employees uh all the data for the table
And the third view is edit view. So in this case we're returning the edit form And over here uh in the get methods uh we are getting uh uh by the primary key we are getting uh entry for the employee to be displayed in an edit form and we're constructing employee model form with the instance of employee uh including this form into the context and returning back with the template. And this is how edit form is displayed. And this post method for this view because we're handling a data update uh uh in this view. So what happens here uh uh uh
actually what we do here is uh uh we get information about the employee data that we're updating right now We are construct constructing the instance of the form and we are validating the form. And if and this will trigger all those validations defined in a model form , also in the model, all the constraints and so on. If everything is fine, then if there are no validation errors, we are hitting save. Data is saved and then we're turning success, returning the same form back and Also we're in in uh in response we are including HTMX trigger uh employ with name employee changed and uh when this
uh when this um event employee change would be uh received and detected by HTMX then uh we'll have uh subscriber logic which will trigger refresh for the table and this refresh for table would execute employee table view and uh fetch fresh data for to be displayed in table. In case of the error then uh when Not doing anything like that. We are then just collecting list of errors and we're returning the same template, the same template which is added from HTMX. And we are attaching to this template list of errors that will be displayed on UI.
Now we will see how to implement HTMX get. So when we are hitting edit button , this one. So we click edit, right? And we get back edit form visible. Visibility is controlled by Alpine uh GS and uh we are fetching uh uh employee data uh through HTMX get request So we are not copying data from the table, we are executing separate requests. And this could be valid for multiple reasons. One of them is that maybe not all data would be present um
in an HTML table roll and maybe you would want to fetch additional uh fields uh for the edit form so you would like to execute a separate request Then if you look into this button definition, yeah, so this is Alpine GS stuff over here. And then we get um uh we define uh attributes on on top of html button that are specific to htmx library so first one uh get then there's a target and swap So in GET we execute Django expression language using URL expression
and we are referring to the edit employee. uh URL entry which is defined in URLs file, URL script and we uh Passing as URL parameter employee ID. So from the current records that we are working right now where user is pressing edit we are getting employee ID from employee object because all the roles are displayed in a loop and employee refers to the current role. And as a target for this uh HTMX
get event, uh we are specifying edit form. So this is over here. If you go Scroll up. This is the edit form is defined so this is form which renders edits editable data. And we're using swap attribute outer HTML. This means when response will come back. For the target edit form, we would like to replace it with whatever HTML response would come from the back end. We would redraw it. basically and let's look into the uh what is the edit employee so edit employee is defined over here This one and it's a jungle
puff that points to the edit employee. We pass primary key and we are referring view class employee edit view So if we open it again, so employee edit view is over here and we'll execute get request. We get primary key from the URL parameter and we'll get we get employee model, then we initialize instance of employee form and we pass it back with the through the context. and we are passing data uh uh the form uh context and we are passing uh uh template name edit form htmx this one and uh all of this is uh sent back uh
as a jango for the Django render function and HTMX is taking care or for this definition. It says that it will replace target with id edit form So whatever response is coming back, uh it will be included into this target. Right, and It's important to mention that if you look into the edit edit form, so each of the edit elements is implemented with a render field For example, and we are referring here here to the form, first name, and so on. So form uh is coming from the context, and this is the attribute name we're using placeholder uh and using uh class from the flow byte to
uh have a nice um representation of this ui component component using tailwind uh uh implemented by the flow byte, right? And to render the form fields to automatically get validation functionality and so on, we are using if you look into the Base settings and on top of that over here we're using um um widget twix uh plugin which helps to uh render nicer uh form elements uh for for Django And if you go back to the page
if um yeah, if I think we if I try to Type something like that and uh see if yeah it gets invited email is expected and uh Yeah, if the first name or if I remove the last name, because this last name is set as required field in Django, then we get We get back the information that please fill in this field. And yeah, this is comes from uh from those
widget tweaks out of the box So we don't need to call this uh functionality. So part of the validations uh returned like this one when uh when on on model fields you define uh attributes uh like uh length or required for example then those kind of uh errors will be reported immediately on the field And all other validations for uh like constraints and validations that you define in the model form, they'll be they will be reported on safe over here. And let's see also what happens when we hit edit button. Let's see what kind of request is coming back. So if you go to the
Um developer over here and developer tools network And I hit edit button. We see that get request for employee with ID5 was executed. Okay, sorry for that. And if I move it a bit up under the Request headers, uh key, uh this bunch of stuff. sprinted and this response and let's see the actual response that is coming back.
So this is It's a not whole response of the whole HTML page as you would expect, but this is the partial response. It's only the form is included, this edit form that we see here. And yeah, this is this response is based on HTML template as it as we have defined. And the name for this template is edit formhtmx and in views over here and uh in employee edit view uh we are returning edit form htmix over here and we're returning um instance of uh employee forum that was uh constructed based on a primary key
okay so Yeah and and and later it's HTMX job uh to include this uh response into the main HTML page and uh render it without refreshing the whole stuff Okay, we can close it and now okay. Let's open another record. Yeah, let's open maybe some some uh different record, this one for Alexander. And uh now let's look into uh functionality of uh when we are actually clicking save button and what happens when uh we click save button. So for that we go to the edit form
and below here we have a save button it's of type submit and this map this button is wrapped by the form And this means when we hit submit button then form will be submitted. And on top of the form tag, we uh using HTMX post trigger swap and include attributes. And this hidden input defined with name ID and from the form instance we are passing primary key of the current data that is displayed of ID of the current employee. And this hidden input will be used by HTMX post request to
send it to the to the backend. So we have a post and uh again we're using um jungle Django expression here uh to reference edit employee URL And I experienced the problem with initial rendering when this edit form is hidden, then um because edit employee uh URL pattern expects primary key, then it it it breaks if there is nothing no no uh parameter passed by default when form is uh in a hidden state. So I'm including zero and it's working fine and later when We have a real primary key, we are sending it through this hidden variable, so as a work round in this case.
So we have a uh we are using uh as a trigger for this execution submit and uh uh this is the the subs uh submit uh event we are getting from the but from this uh from the button and we swap um outer HTML uh basically uh because we are uh re uh redisplaying rendering the same form so uh the form content will be displayed uh with the uh again when response will come Yeah, and if you look into the URLs, so uh edit employee uh is the same path as we were using two
uh fetch employee data by key so in this case we are using the same path but we are posting data uh using the key Okay, and uh this is um visible under employee edit view class as well because we have a we override post methods over here and uh what this post method does uh it reads uh hidden uh variable uh from the request id if it's not known uh we are getting uh getting it and uh uh assigning it to the primary key uh variable and uh getting the uh employee records for this primary key
and uh constructing the form instance uh with the employee uh uh information and all the values that were updated on the UI in form they are automatically submitted uh with um the ID with um field names and uh Django automatically maps uh uh values uh with field names uh uh it maps all this data that is coming from the UI uh it maps with the actual uh model form fields. And populates the model form with with the new data, and later when we execute form is valid, then uh it validates uh
new data. And as I mentioned before, if validation is successful, then we are actually saving the data and returning back the context with um uh along with htmx event. If uh there are validation errors then uh we are returning list of errors and display it on UI Okay, okay. Okay, so next uh we'll see how the table is updated when uh we are changing data in edit form. So like in in this case Let's uh uh say that we update the salary to be 9500
save and to check for Alexander the salary was updated Okay, and also if we look um again into the developer tools, let's see uh what kind of um network uh action happening in this case. So let's say that again we do one more update 9600 hit save So we get in this case one post request, so we are submitting the data. And in response we're getting back the form Then uh we get back to
uh I think There are two GET requests. Um yeah, so and in response we get back the table over here and uh over here so there are basically two identical get requests this is not ideal but um yeah this is how it works and I I didn't manage to find find solution to uh run single request in this case uh but yeah maybe it will be improved in the future But uh the whole point how it works, uh it's it's using HTMX uh event. So let's look uh into the flow again. So what happens when we hit save button? When we hit save button we submit a form with HTMX
post And we actually put we include the hidden uh element uh ID or here uh to be able to uh refer to the employer ID that we are updating right now. Okay, and then we look into the views. So this is the post thing and when there are no validation errors when everything is fine uh we hit save and do we construct the response context With the message success message which says data saved successfully. So we we construct the response this way
And additionally we include HX Tigger with the name employee change. Employee change is a custom name. And we return back this response. Right, and what happens then is uh because what we want to do, we want to refresh a table, right? Uh since we updated the data in edit form And we have on top we have a table that displays the same data and we want this table to be refreshed automatically in the same request. We don't want to hit any other button to reload the table. And we got the table inside view table HTMX over
here and this is the table definition And uh we define on top of a table tag we define HTMX get. We refer to employees. And if you double check Uh here we have employees path defined right here. Employees path. It refers to employees slash and we refer to employees table view. And if we go back to the views, then this is where we have employees table view. So employees table view is very similar to the index view. The only difference that uh it's using uh different template So probably uh you could um
we could use a single class and have some ch uh check uh from where um this view is being initialized right now, but I thought for the simplicity and for the demonstration purpose to put those views separately. And what we have here is we get all the employees and again we include those employees into the context and return with Django render. return the context and also return view table HTMX as a template. And viewtable HTMX is actually the template that contains table itself. So we return back the table and return back the new data that will include
all the changes that were saved already uh inside uh employee edit view over here uh because we saved and we returned back the event And if you look on the table tag again, we'll see that we are using new uh HTMX attributes over here. It's called Tigger. And uh oh here we need to specify event name employee changed and this is equal to employee change event from here from the post and uh we specify that we listen for the employee change event from
body And body is a root uh tag in this page and uh when event is propagated then it'll it will be uh catch by the body anyway and uh HTMX will be able to catch the event and when this event happens then the magic HTMX magic happens and it 's able to detect the event And when event is detected, then on top of this table uh element uh HTMX get request will be executed And since we don't specify any target here, this means by default target will be uh the the same tag by itself. And
uh uh yeah when response comes since we are returning the same uh template for the for for the content then it'll just it means that the the current table will be simply replaced Table will be replaced with the new HTML structure and with uh new data. So it will be uh re-rendered. And if we go back to the UI and do a couple of more tests, we'll see how it happens. So let's say that we close this block and Let's uh open uh Lex, the further employee. And if We go and
change the job, maybe as accountant, click Safe yeah it would not work because uh probably accountant uh salary is not in range with the current uh lack salary so it would not uh work to change uh we we return back it to return back to be administration vice president and um if we maybe change the manager to nina Yeah, then it worked. And if you go back we see that uh the information about the manager was instantly updated to Nina. This means uh as expected this functionality works great because we work on uh
one section of the screen, do changes there, execute request. And through HTMX events in different areas of the screen we can catch event and refresh the data. Let's look into the error reporting. So there are a few cases when we have we report errors in this application. For example, when we want to update uh salary to be negative then we get um invalid salary must be in range but actually yeah this constraint uh that is enforcing uh value to be uh positive but before that we check salary to be in range also so um in this case it's fifteen thousand so if we uh say that's who would
like to update to 10,000 then the same validation error will be displayed also if um we change the email to be non-unique value then another validation error is displayed um what else did if uh we try to save data and remove value for the required fields then we'll get field level validation and this out of the box uh with um it comes from those um from from the plugin uh which renders the form elements uh it shows this kind of uh uh errors that are defined um based on uh uh
Django model uh attributes uh like required one uh this kind of thing is displayed directly to the on top of the field So we don't need to worry about that. But uh about other validation errors that um uh enforced on model form or about um constraints that are defined on the model we need to think how to report them and uh in this case we report them below the table just uh one by one And on the other hand, if we fix the fix it, uh for example, say this one is uh sixteen thousand and uh uh email will be l The HAN for example we saved and we get the message that
message uh data is saved successfully in this case. So uh the way how it's implemented, first of all Either successful or error messages are reported from view from the post method for the employee edit view over here. If it's success, then it's included as a Success context either error context and later inside the edit form Here below we got div with feedback and we include partial info HTML. So this this is the partial is uh over here and inside this partial we have uh two blocks uh that are wrapped into the jungle
loops. So first one is loop that displays errors, the second one is that displays success messages. And we are using flow byte styling to display those alerts And uh yeah, as many error messages we have in a loop we display all of them. If there are none uh then basically loop is empty and uh no messages uh displayed. And this means every time when uh we return uh this uh block for the editable form uh also info uh partial is being uh evaluated and uh it che we check here if there are any errors then Uh basically text about errors will display it, and if um success messages list is not empty, then it also will be displayed.
Yeah, so it's quite simple. And this means later on the next request, if we fix validation error and there's no error anymore, then uh list with uh errors is empty and uh it simply doesn't print out any uh any message anymore so this is this is how uh error handling is done in this application So hopefully um yeah it was useful for you and uh you'll find uh some practical uh value for your own use cases. I was trying to keep it as um uh simple and practical as possible. Source code for this application is available on GitHub and uh yeah it's uh you can refer to it and uh try to run
application on your own environment and uh Implement the same stuff for your own use cases. So thanks and yeah, it was a pleasure.
HTMX lets a Django application send partial requests and replace only the relevant parts of the page. In the example, saving an edit updates the table without reloading the static content or the entire page.
Discussed at 1:52Alpine.js stores a `showEditForm` state variable. Clicking Edit sets it to true, making the form visible and disabling the other edit buttons; Cancel sets it back to false.
Discussed at 6:28A `ModelForm` can select the fields and widgets displayed in the UI and define methods such as `clean_email` and `clean_salary`. Django runs these validations automatically when the form is submitted, returning validation errors to the UI and preventing an invalid save.
Discussed at 11:02The Edit button issues an HTMX GET request for the selected employee, targeting the edit-form element and swapping its outer HTML. Django returns only the form partial, populated from the employee’s primary key, rather than a complete page.
Discussed at 19:25The form uses an HTMX POST and includes the current employee ID in a hidden input. Django binds the submitted field values to a model form, validates them, saves the employee when valid, and returns the form again with errors when validation fails.
Discussed at 28:39After a successful save, the Django view returns a custom `employeeChanged` HTMX event. The table listens for that event and makes a GET request for a table-only template, replacing the old table with the newly queried data.
Discussed at 34:48The view passes success or error messages back in the form response, and a feedback partial renders them with Flowbite styling. Field-level errors appear beside fields, while model-form and model-constraint errors are listed below the table; once corrected, the old errors disappear and a success message is shown.
Discussed at 43:12Note: We understand that names change, people change, and bodies change. We respect each individual's journey and privacy. If you have any concerns about a video or need us to remove content, please don't hesitate to contact us. We will handle your request with care and promptly address any issues.
Published July 15, 2026
Published July 15, 2026
Published July 15, 2026
Published July 15, 2026
Published July 15, 2026
Published July 14, 2026