<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Happiness Nwosu]]></title><description><![CDATA[Writing about software engineering, system design, debugging, cloud architecture, career growth amongst other things]]></description><link>https://www.happinessnwosu.com</link><image><url>https://substackcdn.com/image/fetch/$s_!CP-b!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F81bb4e77-51eb-4105-bf6c-75985e89f47d_144x144.png</url><title>Happiness Nwosu</title><link>https://www.happinessnwosu.com</link></image><generator>Substack</generator><lastBuildDate>Sun, 28 Jun 2026 18:08:01 GMT</lastBuildDate><atom:link href="https://www.happinessnwosu.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Oriaku]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[happinessnwosu@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[happinessnwosu@substack.com]]></itunes:email><itunes:name><![CDATA[Happiness Nwosu]]></itunes:name></itunes:owner><itunes:author><![CDATA[Happiness Nwosu]]></itunes:author><googleplay:owner><![CDATA[happinessnwosu@substack.com]]></googleplay:owner><googleplay:email><![CDATA[happinessnwosu@substack.com]]></googleplay:email><googleplay:author><![CDATA[Happiness Nwosu]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[A simple OpenAI model upgrade that was not so simple]]></title><description><![CDATA[In the age of AI and LLM-generated content running services, the value of end-to-end tests cannot be overstated]]></description><link>https://www.happinessnwosu.com/p/a-simple-openai-model-upgrade-that</link><guid isPermaLink="false">https://www.happinessnwosu.com/p/a-simple-openai-model-upgrade-that</guid><pubDate>Tue, 09 Jun 2026 16:10:23 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!CP-b!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F81bb4e77-51eb-4105-bf6c-75985e89f47d_144x144.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>So, I have been using gpt-4.1-mini to generate short product descriptions in a product workflow and it while it worked fine, I  still got some slops, hallucinations and inaccuracies. </p><p>After some recent generated errors and hallucinations, It sounded like a good time to switch to newer models. At the time this sounded like an easy change as all I needed to do was to swap the versions and move on to other things.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.happinessnwosu.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe to receive new posts and interact with my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>The goal was simple, I wanted:</p><ul><li><p> less hallucinations and errors</p></li><li><p> more accurate descriptions</p></li><li><p> reduced cost from reduced retries since this upgrade should give me less validation errors from the generated output.</p></li></ul><p>So we went from gpt-4.1-mini to gpt-5-mini. I updated the package, updated the code. Small change. Then the end-to-end tests started failing.</p><h5>First Error Encountered</h5><p>max_tokens is deprecated in favor of max_completion_tokens.</p><p>At first glance, this looks fine. small issue. </p><p>max_tokens was added in the first place because at one point the models would generate way too much text for a product image that only needed one short description. So the limit was there for a reason. </p><p>I needed to keep it short and straight to the point and also save cost on output tokens. This also helps in cases where the model generates gibberish as it limits how much gibberish I have to pay for in output tokens cost. </p><p>So I changed it to max_completion_tokens, updated the code again and deployed.</p><p>Ran the tests.</p><p>New error.</p><h5>Second Error Encountered</h5><p><strong>completion.choices[0]?.message?.content</strong> came back empty.</p><p>I know this because I process that as an error when the content length comes back empty. This is an important validation as the whole point of the feature is to return a usable description. If there is no content, there is nothing useful to validate on use on product page.</p><p>So I logged the completion and saw this:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;json&quot;,&quot;nodeId&quot;:&quot;65077c61-f642-4ca6-b9fe-c5e9a6c4ade0&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-json">{
    "model": "gpt-5-mini-2025-08-07",
    "choices": [
      {
        "message": {
          "role": "assistant",
          "content": ""
        },
        "finish_reason": "length"
      }
    ],
    "usage": {
      "prompt_tokens": 1204,
      "completion_tokens": 350,
      "completion_tokens_details": {
        "reasoning_tokens": 350
      }
    }
  }</code></pre></div><p>Here you will see:</p><ul><li><p>  <strong>content</strong> is empty</p></li><li><p><strong>finish_reason</strong> is &#8220;length&#8221;</p></li><li><p>  All the allocated <strong>max_completion_tokens</strong>  were used for reasoning.</p></li></ul><h5>So, what is going on?</h5><p>Going back to the commented docs in the openai package you see:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;javascript&quot;,&quot;nodeId&quot;:&quot;c1f1c975-d9c5-4c0a-8429-82fc7f3f05da&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-javascript"> /**
   * An upper bound for the number of tokens that can be generated for a completion,
   * including visible output tokens and
   * [reasoning tokens](https://platform.openai.com/docs/guides/reasoning).
   */</code></pre></div><p>visiting the guides on <a href="https://platform.openai.com/docs/guides/reasoning">reasoning models</a> it was clear that gpt-5-mini is a reasoning model, and this this new parameter <strong>max_completion_tokens</strong> has a different meaning than the previously used <strong>max_tokens.</strong></p><p><strong>max_completion_tokens</strong> is not just &#8220;how much text can the model return&#8221; anymore but it now includes the internal reasoning tokens the model uses before it gives the final answer. Basically, the  parameter now behaves differently depending on the model you are using.</p><p>With a reasoning model, that <strong>max_completion_tokens</strong> limit is now shared between:</p><ul><li><p>  reasoning</p></li><li><p>  actual visible text output</p></li></ul><h5>Why this is a problem</h5><p>The challenge here is not that reasoning tokens exist, this is fine. The challenge is that the parameter becomes more ambiguous from an engineering point of view. At least in this particular use case.</p><p>Now it is not straightforward to predict how much of that token budget will be used for reasoning and how much will be used for the final text I actually need and that  matters because I still want the same behavior I had before updating this llm model.</p><ul><li><p> I do not want the model generating a full book when I asked for one short description</p></li><li><p>  I want to cap costs at an output token in case the model decides to generate gibberish anyway</p></li><li><p>  I want retries to happen only when necessary</p></li><li><p>  A somewhat predictable behavior. I know, this is llm generated output and it is non-determinisitic, but this is one of many levers helping to handle this non-deterministic nature</p></li></ul><p> Also, this is not some deep reasoning task. I just needed short, accurate, controlled text output. With this setup, you might spend tokens, hit the limit, and still get nothing visible back. So yes, this can increase cost in ways you may not be expecting.</p><p>As I write this though, I wonder if thi parameter could have been split into two parts such as <strong>max_output_tokens </strong>and <strong>max_reasoning_tokens. </strong> This might help users better budget tokens and in addition to <strong>reasoning.effort </strong>get more &#8220;predicatble&#8221; results based on their use-case.</p><h5>Why end-to-end tests mattered here</h5><p>This is one of those cases where end-to-end tests actually did their job. If one only looked at the code change, this would have seemed fine.</p><ul><li><p>  The package updated.</p></li><li><p>  The model changed.</p></li><li><p>  The request still went through.</p></li></ul><p>But the actual output behavior changed and that is the part that matters when building with LLMs. The non-deterministic nature makes a lot difficult to test so it is important to actually set some tight validations on the generated output. This can include text length validation, text patterns check and matching, output accuracy, emojis generated when not present in input amongst others.</p><h4>tldr;</h4><p>Upgrading an LLM model can change more than output quality, it can also quietly change token budgeting in ways that increase cost and break assumptions even for a change such as llm model update.</p><p>This is why due diligence with end-to-end testing matters in LLM systems: it is often the only way to catch generated output inconsistency, validation issues, and cost regressions before they reach production.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.happinessnwosu.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe to receive new posts and interact with my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Django management commands from admin interface]]></title><description><![CDATA[This need came up when during app deployment, needed to run some management commands without ssh-ing into the server or using the terminal.]]></description><link>https://www.happinessnwosu.com/p/call-management-commands-from-admin-interface-django</link><guid isPermaLink="false">https://www.happinessnwosu.com/p/call-management-commands-from-admin-interface-django</guid><dc:creator><![CDATA[Happiness Nwosu]]></dc:creator><pubDate>Thu, 26 Oct 2017 12:37:49 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/ce32c64e-d66c-4152-904c-db8ad7e7cc73_1980x556.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This need came up when during app deployment, needed to run some management commands without ssh-ing into the server or using the terminal. Thankfully Django management command can be called anywhere using management.call_command()</p><p>This is a brief one, view code on GitHub here: <a href="https://github.com/NEbere/data-import/tree/master">https://github.com/NEbere/data-import/tree/master</a></p><p>You need to install the <strong>admin_plus</strong> package with pip, ensure your virtual environment is setup and activated if that's what you're using, in any case, ensure you have the right environment setup and running for your app before installing to avoid any issues :wink:</p><p>Install admin_plus: </p><pre><code>pip install django-adminplus</code></pre><p>Add <strong>adminplus</strong> to INSTALLED_APPS in your settings file</p><p><strong>If you&#8217;re using Django 1.7, you should also replace django.contrib.admin with django.contrib.admin.apps.SimpleAdminConfig in your installed apps, to disable the automatic auto-discovery:</strong></p><p>So update 'django.contrib.admin' to 'django.contrib.admin.apps.SimpleAdminConfig' in INSTALLED_APPS in your settings file</p><p>import <strong>AdminSitePlus</strong> in urls.py of your main package, the file where URL's from installed apps are registered <strong>hint:</strong> this is where you have the admin URL defined</p><p>In that same file, add this for the admin_plus </p><pre><code>admin.site = AdminSitePlus() admin.autodiscover() </code></pre><p>Now, head to the admin.py file in your app and call the command you want to execute.</p><pre><code>from django.contrib import admin
from .models import Movie
from django.core import management
from django.shortcuts import redirect


class MovieAdmin(admin.ModelAdmin):
    @admin.site.register_view(&#8217;import_movies_from_url&#8217;, &#8216;Import Movies from URL&#8217;)
    def import_movies_from_url(request):
        print(&#8217;import movies here&#8217;)
        try:
            management.call_command(&#8217;import_from_url&#8217;)
            message = &#8216;successfully imported data from URL&#8217;
        except Exception as ex:
            message = &#8216;Error importing from data from URL {}&#8217;.format(str(ex))
        admin.ModelAdmin.message_user(Movie, request, message)
        return redirect(&#8217;admin:index&#8217;)


admin.site.register(Movie, MovieAdmin)</code></pre><p>The command I am calling here is <strong>import_from_url</strong> and that name corresponds to the file where I defined the command (minus the file .py file extension)</p><p>This does two things: 1. Makes your Model available on the admin interface, you can create, update, delete models objects from the admin interface once your models are registered. 2. Provides a link that, when clicked, runs the management command defined to be run.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!pNYr!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7dad0296-2f07-4106-80c4-eea2bd0f4981_1980x556.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!pNYr!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7dad0296-2f07-4106-80c4-eea2bd0f4981_1980x556.png 424w, https://substackcdn.com/image/fetch/$s_!pNYr!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7dad0296-2f07-4106-80c4-eea2bd0f4981_1980x556.png 848w, https://substackcdn.com/image/fetch/$s_!pNYr!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7dad0296-2f07-4106-80c4-eea2bd0f4981_1980x556.png 1272w, https://substackcdn.com/image/fetch/$s_!pNYr!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7dad0296-2f07-4106-80c4-eea2bd0f4981_1980x556.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!pNYr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7dad0296-2f07-4106-80c4-eea2bd0f4981_1980x556.png" width="1980" height="556" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7dad0296-2f07-4106-80c4-eea2bd0f4981_1980x556.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:556,&quot;width&quot;:1980,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Screen Shot 2017-10-26 at 1.36.02 PM&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Screen Shot 2017-10-26 at 1.36.02 PM" title="Screen Shot 2017-10-26 at 1.36.02 PM" srcset="https://substackcdn.com/image/fetch/$s_!pNYr!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7dad0296-2f07-4106-80c4-eea2bd0f4981_1980x556.png 424w, https://substackcdn.com/image/fetch/$s_!pNYr!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7dad0296-2f07-4106-80c4-eea2bd0f4981_1980x556.png 848w, https://substackcdn.com/image/fetch/$s_!pNYr!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7dad0296-2f07-4106-80c4-eea2bd0f4981_1980x556.png 1272w, https://substackcdn.com/image/fetch/$s_!pNYr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7dad0296-2f07-4106-80c4-eea2bd0f4981_1980x556.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>More details on <a href="https://pypi.python.org/pypi/django-adminplus">admin_plus</a></p><p>Again, here is the GitHub repo with all the import scripts and sample files: <a href="https://github.com/NEbere/data-import/tree/master">https://github.com/NEbere/data-import/blob/call-management-command-from-admin/</a> </p><p>Happy coding :)</p>]]></content:encoded></item><item><title><![CDATA[Import data into database - django app]]></title><description><![CDATA[Steps to import data into your Django app]]></description><link>https://www.happinessnwosu.com/p/import-data-into-database-django-app</link><guid isPermaLink="false">https://www.happinessnwosu.com/p/import-data-into-database-django-app</guid><dc:creator><![CDATA[Happiness Nwosu]]></dc:creator><pubDate>Tue, 17 Oct 2017 17:01:22 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!CP-b!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F81bb4e77-51eb-4105-bf6c-75985e89f47d_144x144.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>Steps to import data into your Django&nbsp;app</strong></p><p>So you have a Django app and want to load data from a URL, a CSV file or a JSON file, I got you :) First, create your models, make migrations, migrate and all that setup you have to do, but I'm sure you got all that covered :wink:</p><p><strong>See it on Github here:</strong> <a href="https://github.com/NEbere/data-import/tree/master">https://github.com/NEbere/data-import/tree/master</a></p><h4>Import from URL:</h4><p> This code gets data from a URL, be sure to have requests installed or any other HTTP library of choice.</p><pre><code>&#8220;&#8221;&#8220; Import json data from URL to Database &#8220;&#8221;&#8220;
import requests
import json
from import_data.models import Movie  # Import your model here
from django.core.management.base import BaseCommand
from datetime import datetime

IMPORT_URL = &#8216;https://jsonplaceholder.typicode.com/photos&#8217;  # URL to import from


class Command(BaseCommand):
    def import_movie(self, data):
        title = data.get(&#8217;title&#8217;, None)
        url = data.get(&#8217;url&#8217;, None)
        release_year = datetime.now()
        try:  # try and catch for saving the objects
            movie, created = Movie.objects.get_or_create(
                title=title,
                url=url,
                release_year=release_year
            )
            if created:
                movie.save()
                display_format = &#8220;\nMovie, {}, has been saved.&#8221;
                print(display_format.format(movie))
        except Exception as ex:
            print(str(ex))
            msg = &#8220;\n\nSomething went wrong saving this movie: {}\n{}&#8221;.format(title, str(ex))
            print(msg)

    def handle(self, *args, **options):
        &#8220;&#8221;&#8220;
        Makes a GET request to the API.
        &#8220;&#8221;&#8220;
        headers = {&#8217;Content-Type&#8217;: &#8216;application/json&#8217;}
        response = requests.get(
            url=IMPORT_URL,
            headers=headers,
        )
        response.raise_for_status()
        data = response.json()
        for data_object in data:
            self.import_movie(data_object) </code></pre><h4>Import from files (JSON):</h4><p> To import from a file, read the file location</p><pre><code>import os  # Add os import to the imports used above for URL

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))  # Define BASE_DIR or import of it is previously defined


class Command(BaseCommand):
    def import_movie_from_file(self):
        data_folder = os.path.join(BASE_DIR, &#8216;import_data&#8217;, &#8216;resources/json_file&#8217;)
        for data_file in os.listdir(data_folder):
            with open(os.path.join(data_folder, data_file), encoding=&#8217;utf-8&#8217;) as data_file:
                data = json.loads(data_file.read())
                for data_object in data:
                    title = data_object.get(&#8217;title&#8217;, None)
                    url = data_object.get(&#8217;url&#8217;, None)
                    release_year = datetime.now()
                    # Use the try and catch used above here as it&#8217;s the same model
                    try:
                        movie, created = Movie.objects.get_or_create(
                            title=title,
                            url=url,
                            release_year=release_year
                        )
                        if created:
                            movie.save()
                            display_format = &#8220;\nMovie, {}, has been saved.&#8221;
                            print(display_format.format(movie))
                    except Exception as ex:
                        print(str(ex))
                        msg = &#8220;\n\nSomething went wrong saving this movie: {}\n{}&#8221;.format(title, str(ex))
                        print(msg)

    def handle(self, *args, **options):
        &#8220;&#8221;&#8220;
        Call the function to import data
        &#8220;&#8221;&#8220;
        self.import_movie_from_file()  # call the import function in handle</code></pre><h4>Import from files (CSV):</h4><p> To import from a csv uses the same steps defined for JSON file import above with the difference been importing csv, and how the data is parsed.</p><pre><code>import csv  # Add csv import to the imports used above for JSON import

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))  # Define BASE_DIR or import of it is previously defined


class Command(BaseCommand):
    def import_movie_from_csv_file(self):
        data_folder = os.path.join(BASE_DIR, &#8216;import_data&#8217;, &#8216;resources/csv_file&#8217;)  # folder where csv file is stored
        for data_file in os.listdir(data_folder):
            with open(os.path.join(data_folder, data_file), encoding=&#8217;utf-8&#8217;) as data_file:
                data = csv.reader(data_file)
                next(data)  # Skip header row if present
                for data_object in data:
                    title = data_object[1]  # the value at index 0 is the ID and we dont need that here
                    url = data_object[2]
                    release_year = datetime.now()
                    # Use the try and catch used above here as it&#8217;s the same model
                    try:
                        movie, created = Movie.objects.get_or_create(
                            title=title,
                            url=url,
                            release_year=release_year
                        )
                        if created:
                            movie.save()
                            display_format = &#8220;\nMovie, {}, has been saved.&#8221;
                            print(display_format.format(movie))
                    except Exception as ex:
                        print(str(ex))
                        msg = &#8220;\n\nSomething went wrong saving this movie: {}\n{}&#8221;.format(title, str(ex))
                        print(msg)

    def handle(self, *args, **options):
        &#8220;&#8221;&#8220;
        Call the function to import data
        &#8220;&#8221;&#8220;
        self.import_movie_from_csv_file()  # call the import function in handle</code></pre><p>Run the import commands</p><pre><code>python manage.py import_from_url</code></pre><p>import_from_url is the name of the file where the command is defined without the extension, that is, .py</p><p>for more about requests <a href="http://docs.python-requests.org/en/master/">http://docs.python-requests.org/en/master/</a></p><p>Again, here is the GitHub repo with all the import scripts and sample files: <a href="https://github.com/NEbere/data-import/tree/master">https://github.com/NEbere/data-import/tree/master</a> Happy coding :smile:</p>]]></content:encoded></item><item><title><![CDATA[Bower install from a forked repository]]></title><description><![CDATA[If for any reason you need to fork a repo and want to install your forked version using Bower, follow these steps:]]></description><link>https://www.happinessnwosu.com/p/bower-install-from-a-forked-repository</link><guid isPermaLink="false">https://www.happinessnwosu.com/p/bower-install-from-a-forked-repository</guid><dc:creator><![CDATA[Happiness Nwosu]]></dc:creator><pubDate>Mon, 29 May 2017 15:17:33 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!CP-b!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F81bb4e77-51eb-4105-bf6c-75985e89f47d_144x144.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If for any reason you need to fork a repo and want to install your forked version using Bower, follow these steps:</p><ol><li><p>&nbsp;Fork the repository</p></li><li><p>Clone the forked repo on your computer</p></li><li><p>Make desired changes</p></li><li><p>Increment version number in bower.json (create one if none exists e.g. 4.0.1)</p></li><li><p>Commit and push</p></li><li><p>Create a new version tag higher than that of the forked repository. If version tag from respository is 4.0.1, increase it. e.g. 4.0.2 :</p></li></ol><pre><code>$ git tag "4.0.2"&nbsp;</code></pre><p>push tag </p><pre><code>$ git push --tag</code></pre><p>Install in your app using Bower:</p><pre><code>$ bower install https://github.com/yourName/forkedRepository.git#4.0.2</code></pre>]]></content:encoded></item><item><title><![CDATA[Update a model field - Django]]></title><description><![CDATA[I noticed a typo in a field name after running migrations, I followed these steps to update the field without loosing data.]]></description><link>https://www.happinessnwosu.com/p/update-a-model-field-django</link><guid isPermaLink="false">https://www.happinessnwosu.com/p/update-a-model-field-django</guid><dc:creator><![CDATA[Happiness Nwosu]]></dc:creator><pubDate>Wed, 03 May 2017 23:51:29 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!CP-b!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F81bb4e77-51eb-4105-bf6c-75985e89f47d_144x144.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I noticed a typo in a field name after running migrations, I followed these steps to update the field without loosing data.</p><ol><li><p>Update the filed name in the model with the desired name (taking note of the current name)</p></li><li><p>Create an empty migration. If you have previous migrations, this one must be named in such a way that it comes last in the migrations list. eg if you already have 001_users and 002_emails then this new migration needs to be named 003_new_name</p></li></ol><p> The new migration should look like this:</p><pre><code>from __future__ import unicode_literals
from django.db import migrations

class Migration(migrations.Migration):

    dependencies = [
        (&#8217;django_app_name&#8217;, &#8216;last_previous_migration&#8217;),  # last_previous_migration is the migration immediately before this new empty migration without the .py extension
    ]

    operations = [
        migrations.RenameField(
            model_name=&#8217;model_name&#8217;,
            old_name=&#8217;old_filed_name&#8217;,  # that was taken note of from step one above
            new_name=&#8217;description&#8217;,  # the new/desired field name
        ),
    ]</code></pre><p>3. run migrations</p><pre><code>$ python manage.py migrate</code></pre><p>This will update the field.</p><p>Hope this saves someone some googling time</p>]]></content:encoded></item><item><title><![CDATA[virtualenv for python3]]></title><description><![CDATA[Simple steps to setup virtualenv for Python3]]></description><link>https://www.happinessnwosu.com/p/virtualenv-for-python3-ubuntu</link><guid isPermaLink="false">https://www.happinessnwosu.com/p/virtualenv-for-python3-ubuntu</guid><dc:creator><![CDATA[Happiness Nwosu]]></dc:creator><pubDate>Mon, 01 May 2017 21:02:16 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!CP-b!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F81bb4e77-51eb-4105-bf6c-75985e89f47d_144x144.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>Simple steps to setup virtualenv for Python3</strong></p><p>To setup virtualenv for python3 for Django projects, create and cd into the desired folder for the Django project.</p><p>Create a folder for virtualenv, </p><pre><code>$ mkdir venv
$ cd venv </code></pre><p>Then run these&nbsp;commands worked for me in this order:</p><ul><li><p>setup virtualenv with Python in the current directory</p></li></ul><pre><code>$ virtualenv -p python3 . </code></pre><ul><li><p> activate virtualenv </p></li></ul><pre><code>$ source bin/activate </code></pre><p>At this point, checking Python version should give python3</p><ul><li><p>Install Django:</p></li></ul><pre><code>$ pip install django</code></pre><p>To see all installations run :</p><pre><code>$ pip freeze </code></pre><p>Django should be included in the list of installations</p><p>Start a Django project</p><pre><code>$ django-admin startproject --projectname</code></pre><p>cd into the created project and run:</p><pre><code>$ python manage.py runserver </code></pre><p><a href="https://www.python.org/dev/peps/pep-0405/">More on virtualenv</a></p>]]></content:encoded></item><item><title><![CDATA[Fix IOError: [Errno 2] No such file or directory error for appdirs]]></title><description><![CDATA[Appdirs is a small Python module for determining appropriate platform-specific dirs, e.g.]]></description><link>https://www.happinessnwosu.com/p/fix-ioerror-errno-2-no-such-file-or-directory-error-for-appdirs</link><guid isPermaLink="false">https://www.happinessnwosu.com/p/fix-ioerror-errno-2-no-such-file-or-directory-error-for-appdirs</guid><dc:creator><![CDATA[Happiness Nwosu]]></dc:creator><pubDate>Fri, 28 Apr 2017 09:20:46 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!CP-b!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F81bb4e77-51eb-4105-bf6c-75985e89f47d_144x144.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Appdirs is a small Python module for determining appropriate platform-specific dirs, e.g. a "user data dir".</p><p>Github repo -&nbsp;https://github.com/ActiveState/appdirs</p><p>This error from my experience occurred when I updated my local branch from origin and ran pip install to get the latest installed packages.</p><p>pip tried to upgrade appdir but it failed for some unknown reasons, then the error came up:</p><pre><code>IOError: [Errno 2] No such file or directory: '/.local/lib/python2.7/site-packages/appdirs-1.4.0.dist-info/METADATA'</code></pre><p>What worked for me was running</p><pre><code>$ pip install appdirs --upgrade </code></pre><p>then:</p><pre><code>$ pip install -r requirements.txt --upgrade</code></pre><p>The issue was addressed here:</p><p>https://github.com/ActiveState/appdirs/issues/89</p>]]></content:encoded></item></channel></rss>