Background
This project began, as I was working, wrapping up Q1 2020 at work and we had a lot of very "rapid fire" changes coming from our customers. These changes caused several revisions to the solution design documents. These design documents are part of my responsibilities as a sales engineer on the product I work on (my company makes several different products). Each time the customer, or internally, we changed something, I would need to go back through my other "in-flight" deals (i.e. the customer hasn't signed the SOW yet) to update the solution design documents to bring them up to the latest and greatest "template". I use the word "template" loosely, as what the template consists of is the latest version of the solution design document ("SDD") in MS Word format, with highlighted blocks of text to be replaced with the relevant information for the next customer (name, solution title, etc.).
The inefficiency of this process for changes is just the beginning, because we have to first create it, in order to change it. To create the document, it can take an hour or two to add in the relevant bits of data, add in the servers (for an on-prem solution), then submit it for internal review from services and the SOW team. They inevitably have changes they want to make, often times it's language and not technical (but sometimes it can be). This causes me to have to rework the SDD, and then resubmit it for approval. Often times these changes are good, they serve a purpose (whether they be internal or customer driven), so this is not a complaint about that. Just the re-work that has to happen across several deals that becomes tedious.
I thought to my self (self), there must be a better way of doing this, certainly this isn't the pinnacle of efficiency, right? It occurred to me that the real problem is that in this scenario, we have two streams of information: (1) the data going into the document, consisting of the solution details, technical specs, etc. And, (2) the template language itself. Both coexisting in the Word doc at the same time, meaning, if I update one I am updating the other, thus forcing rework for the other deals. I further thought, what if I could keep both streams separated until I need thm to be joined (looking at the solution scope, updating the solution design, or exporting it out to a file)? Then I could update the template stream, to fix wording issues, or update the solution, and I don't have to rework things for the other deals ... well technically I still do, but hold that thought.
Let me back up a little bit more, to help frame the rest of this article:
- I am NOT a professional developer, I have a CS degree (Go Comets! which you probably have never heard of, that is the UT Dallas mascot), but haven't really used it. I came from a long IT background and found that having the degree actually didn't do much for me career wise (other than student loan debt of course), but did open a door at my current employer that I would been barred from not having it. Long story short, I am in a sales role where the degree does nothing again (lol). However, it did give me the skill set to look critically at a problem and come up with some ideas on how to solve it. So while I can't code for crap (look at my source code, you will see), I do have that developers eye and can see the problems that way.
- My Python skills are self-taught, I have gone through some Udemy courses, and played around with Python ... man what a great language to learn how to code, or to learn how to code after years of NOT doing it.
- I stumbled onto Django after googling (that's a verb now right?) around for a framework (so I could be somewhat lazy and not build everything from scratch). After doing some reading, and playing around with the massive number of tutorials available, found it to be a fantastic fit for this project, and embarked on building the prototype. After much time spent googling, and searching stack overflow (really, what would I do without that site!), I got my prototype working. BTW this would have been much easier, if I had just purchased Will Vincent's beginners book, and started there. Instead of that I did procure the hard copy of his professionals book and it's great!
The repo is here, let me know if you have any questions.
Ok, so what's this solution I speak of?
It's elementary my dear Watson! I used Django's great framework to build out the database structure (models), the views to translate the database information to the templates (the web portion), such that I can have my two streams, kept separated, yet can be joined together when needed. It kind of looks like this:
MODEL --> VIEW --> TEMPLATE --> PDF
Let me dig into the details here, so you can get a sense.
Model
Let me show you the model itself so you can get a sense:
The Document class has only a couple of columns (I think they are call fields, but since they translate to columns in the DB table, that's how I think of them):
- Name -- pretty self-explanatory, although I tried to use the simple name everywhere, so that it would be super generic and could be used for any project, or one could simply rename it to be more specific or keeping in line with proper naming conventions
- Summary -- as the name suggests, this is a text field where you can put the entire summary of the solution. Ours are typically 2 paragraphs, but this could probably be used to store a lot more. If you are writing a book, I am guessing that TextField has some limits, maybe it's wise to split it up into sections or something ... again this works for me.
- Customer, Product, and Engineer -- these are foreign key relationships, which is a great feature in Django. If I know that there is ONLY going to be a single value for Product, Engineer, and Customer then this is the right relationship, and super easy to implement. Thanks Django!
The rest of the class is pretty boilerplate Django, making sure it shows up properly in lists, with the correct name, and a reverse URL for working with the index value and for testing purposes.
Here is the UML diagram for the Models.py file, so you can see what it looks like.
As you can see the foreign key relationship builds essentially a star schema within the database table structure. So that the main table, in this case document.models.Document has relationships via ID fields to document.models.Product, Engineer, and Customer. It's elegant, and easy to wrap your head around.
The models for Product, Engineer, and Customer are very straight forward, they are just the name field, so I won't go into detail here on them. But this could be expanded upon, to include other fields that may be useful for your model. Examples may include: Customer may have address fields, contact info, a sum of past deals, etc. Product could include details about the product, like family, Line of Business ("LOB"), etc. The sky is the limit here, my use case, what spawned this grand adventure, was a bit more elaborate than this generic version. I had details about servers, line of business, customer details, etc. But I feel like this is a good starting place, to get the ball rolling and flesh it out as needed.
View
Django views are actually, or shall I say, can be, super simple. I ended up using the generic views (ListView, TemplateView, DetailView, and Create/Update Views). It make my view classes super simple, clean, and easy to understand. For example:
In this view I set the model to be my primary model, in this case Document. I set the name of the template file (under project folder > templates > document > detail.html), and for some of the views, where I want to show details, or a list of solution designs, I include the collection of all Document objects return Document.objects.all()
, which provides the query set. These views are pretty basic, and do not allow for a LOT of customization without overriding or using forms.py
to do some customization of the view. As an example, I have in my original project, a many to many relationship to some other tables, and I want to show all the variables that have been selected. Since I wanted to show them as checkboxes that are either checked (selected) or unchecked (not selected), to do that I had to create a forms.py
file to override the default widget. For purposes of this application, I kept things as "stock" Django as possible for ease of understanding and implementing. There is SO much you can do to customize the look and feel, believe me, my browser history is full of searches.
A couple of interesting views come about due to my use of django-weasyprint
, and I pulled the views directly from their github page.
You can find the code here, the views listed in the code worked pretty much out of the box. I tweaked them a little bit to make them work, but otherwise pretty much plug and play. The first class MyModelPrintView
allows you to set a custom response class for a url fetcher, I did not bother with this approach. Mostly because I didn't understand what the use case was, so I skipped it. Besides, I was able to get my solution to work, so why bother right? The DocumentDowloadView
class is what my URLs path entry calls, and spits out a pdf download to my browser, in this case by default, named document_export.pdf
. If I was wanting to use PNG instead of PDF, I could call the DocumentImageView
class instead, but PDF is fine for this solution.
Template
The templates are where you can get creative with how you want the information to be presented. In my solution that I am implementing for work, it's a fair bit more elaborate, mostly because I have to add a TON of formatting and images as part of my solution design documents. However, for this example, I kept things straight forward and simple.
The PDF template, that shows the details and spits out the data in a PDF file, is shown below:
I extend _base.html
to give me a central place to set BootStrap 4, and some general CSS goodness. I am mostly using BootStrap 4 OOTB, nothing fancy here. I included a call to a stylesheet, but it doesn't go anywhere. There are some good tutorials on HTML to PDF for print that should give you some good CSS parameters to use (an example is this page, be warned though that this is an older article, and for me at least, not all of it worked as it showed). I spent a lot of time on this part since I was trying to match a certain style and order to mine, and not everything worked as I thought it should, and I bet I will spend more time working on it to make it more consistent. However, there is so much to learn with CSS that I am sure the issues I am having are just my ignorance and lack of knowledge. But I did read in places that not all CSS tags work when converted into PDF, so be warned. The WeasyPrint documentation is super userful, and I would recommend using it to setup some standard CSS tags for your PDF output.
My index.html
template follows the same template design (extends _base.html
, calls the {% block content %}
), and builds out the view:
It's a simple table that shows 4 columns, takes up the full page, and lists out the list of ALL SDD's in the database. I just simply use a for loop to run through the query set, and produce a line for each element. If you think this will grow into a large number, I would suggest using some logic in your model to filter it down to say, the last 10 objects (and add in a search) or maybe order by the most recent or something. I could imagine this getting out of hand quickly.
For both update.html
and create.html
, those are pretty basic and a copy-paste between the two (with minor wording changes).
Note that I am loading crispy-forms
, you can find details on this guy on pypi.org, and is super easy to work with. I have no doubt that I am just barely scratching the service of this library, but for now I just use the default behavior and it works fine. Otherwise, both create and update are both forms that write to the database, so you have to add in the Django security {% csrf_token %}
otherwise it won't work correctly.
Testing
As Jacob Kaplan-Moss, one of Django’s original developers, says “Code without tests is broken by design.” (BTW I pulled that right out of the Testing chapter of the Django tutorial, full credit goes to that document). But the bulk of my testing knowledge (very limited) comes from Django for Professionals by William Vincent, you can find it here. BTW William Vincent's web site is great for tips and tricks, the book is excellent (one complaint I have is Docker heats up my MBP like you would not believe, maybe I will build a PC for this kind of thing), and he is a regular member of a Django oriented podcast which I listen to regularly. Not that I understand everything (which I do NOT) but it's good to hear from the professionals to learn how much you don't know (lol). In the github repository for this project I made sure I am testing the Custom User model (more info shortly) and the document application. The test.py
file for Document is pulled very nearly word for word from Django for Professionals (I mean, William wrote them and they work, why re-invent the wheel right?). Thankfully though, the test cases are easy to write, read, and understand, which is good.
Custom User Modal
From the Django documentation, the book Django for Professionals, it's apparent that IF you are going to, or could one day, need more than default username/password authentication (i.e. Google Auth, Github, Apple, etc.) then you will need to create a custom user model. And doing so early on is the best practice. I implemented that here in the application, but haven't tied it to anything in particular. For instance, you could easily take this further and tie it to GitHub (tutorial here), or to Google (tutorial here). Either way, I have set it up for you, take it where it needs to go. I think the next logical step however would be to use email and password instead of username and password, but that is just my opinion.
What's next?
I don't know what else to do here on this application, I am certain that there is a LOT more that could be done to make it more generic, or more universal to a lot of different use cases, but for now I am going to stop here, post it up on Dev.to and see what happens. There is likely plenty of code in this application that isn't clean or elegant or correct, but it runs, passes tests, and does what it is supposed to. This is my first article there (well anywhere if I am being honest), it may be total garbage in which case I will stop work on it. However, if you want to add to it, fork from it, whatever, let me know.
Top comments (0)