<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Bastian Scherzinger</title>
    <description>The latest articles on DEV Community by Bastian Scherzinger (@webagentur-scherzinger).</description>
    <link>https://dev.to/webagentur-scherzinger</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4173188%2Fc7ac1801-048c-42ce-8d74-626787731e86.jpg</url>
      <title>DEV Community: Bastian Scherzinger</title>
      <link>https://dev.to/webagentur-scherzinger</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/webagentur-scherzinger"/>
    <language>en</language>
    <item>
      <title>One file for every price: how a small Django site stops contradicting itself</title>
      <dc:creator>Bastian Scherzinger</dc:creator>
      <pubDate>Fri, 09 Oct 2026 13:25:05 +0000</pubDate>
      <link>https://dev.to/webagentur-scherzinger/one-file-for-every-price-how-a-small-django-site-stops-contradicting-itself-old</link>
      <guid>https://dev.to/webagentur-scherzinger/one-file-for-every-price-how-a-small-django-site-stops-contradicting-itself-old</guid>
      <description>&lt;p&gt;I build websites for small local businesses in northern Germany. Most of them are a handful of pages: services, prices, a contact form, a few location pages. And almost every site I take over from someone else has the same bug: &lt;strong&gt;it contradicts itself.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The price on the services page says 500 €, the FAQ still says 450 €, the meta description from two years ago says "from 399 €". The opening hours on the contact page differ from the footer. Nobody notices until a customer quotes the cheapest number back on the phone.&lt;/p&gt;

&lt;p&gt;Here is the setup I use now. Nothing clever, but it has caught real mistakes.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Every fact lives in one Python module
&lt;/h2&gt;

&lt;p&gt;Prices, the company address, the phone number and every promise about time ("reply within 24 hours on workdays", "online in 2 to 4 weeks") live in plain modules under &lt;code&gt;apps/core/data/&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# apps/core/data/pricing.py (names simplified)
&lt;/span&gt;&lt;span class="n"&gt;WEBSITE_WITH_CARE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;        &lt;span class="c1"&gt;# one-off, EUR
&lt;/span&gt;&lt;span class="n"&gt;WEBSITE_HANDOVER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1200&lt;/span&gt;        &lt;span class="c1"&gt;# one-off, EUR
&lt;/span&gt;&lt;span class="n"&gt;CARE_PER_MONTH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;250&lt;/span&gt;
&lt;span class="n"&gt;CARE_MIN_MONTHS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Templates never contain a number. They get it from the context:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jinja"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Website with ongoing care: &lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;pricing.WEBSITE_WITH_CARE&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt; €&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same goes for JSON-LD. The structured data is built in Python from the same module, so the schema can't say something different from the visible page.&lt;/p&gt;

&lt;p&gt;A new page is an entry in a data list, not hand-work on URLs, sitemap, navigation and schema.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. A command that renders every page and checks the numbers
&lt;/h2&gt;

&lt;p&gt;The module alone doesn't help if someone (usually me, in a hurry) types "450 €" into a template anyway. So there is a management command that renders every public page with Django's test client and checks the output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# simplified
&lt;/span&gt;&lt;span class="n"&gt;EURO&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;(\d[\d.]*)\s?€&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;allowed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;all_prices&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;          &lt;span class="c1"&gt;# read from pricing.py
&lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;EURO&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;finditer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;visible_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
        &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;allowed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; € is not in pricing.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It looks at titles and meta descriptions too, because that's where old numbers survive longest. The same idea works for time promises: every "within X hours" on the page has to exist in the module that holds them.&lt;/p&gt;

&lt;p&gt;It runs before every deploy, together with the normal test suite.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. What this does not catch
&lt;/h2&gt;

&lt;p&gt;This is the part I find most useful to write down. A green check means "every euro amount on the page exists in the price module". It does &lt;strong&gt;not&lt;/strong&gt; mean:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the text around the number is correct ("from 500 €" vs "500 €"),&lt;/li&gt;
&lt;li&gt;the number is in the right place (the monthly price shown as a one-off),&lt;/li&gt;
&lt;li&gt;a price in an image or PDF is current.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I note next to every check what it cannot find. A check that has never failed on a real mistake is unproven; I test new ones by putting a wrong number in on purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why bother on a five-page site?
&lt;/h2&gt;

&lt;p&gt;Because for a small business the website is often the only written price list. A contradiction there costs trust on the first call, and it is boring to find by hand. One module plus one command takes an afternoon and removes the whole class of bug.&lt;/p&gt;

&lt;p&gt;If you run a lot of small client sites: what do you use to keep facts consistent across pages? I'm curious whether people solve this in the CMS or in code.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I'm Bastian, I build websites for small businesses in Lübeck, Germany: &lt;a href="https://webagentur-scherzinger.com/" rel="noopener noreferrer"&gt;webagentur-scherzinger.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>django</category>
      <category>python</category>
      <category>webdev</category>
      <category>seo</category>
    </item>
  </channel>
</rss>
