<?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: David</title>
    <description>The latest articles on DEV Community by David (@dbslusser).</description>
    <link>https://dev.to/dbslusser</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3311418%2F23603c17-b649-416d-b51f-f83e0b667b3d.jpeg</url>
      <title>DEV Community: David</title>
      <link>https://dev.to/dbslusser</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dbslusser"/>
    <language>en</language>
    <item>
      <title>Building Spokane Tech: Part 9</title>
      <dc:creator>David</dc:creator>
      <pubDate>Sat, 01 Nov 2025 00:30:00 +0000</pubDate>
      <link>https://dev.to/dbslusser/building-spokane-tech-part-9-1p74</link>
      <guid>https://dev.to/dbslusser/building-spokane-tech-part-9-1p74</guid>
      <description>&lt;h1&gt;
  
  
  &lt;strong&gt;Building Spokane Tech: Part 9&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;Welcome to part 9 of the "Building Spokane Tech" series! In this article, we'll optimize our Docker file for size and efficiency.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;See the live site at: &lt;a href="https://www.spokanetech.org" rel="noopener noreferrer"&gt;https://www.spokanetech.org&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;See the latest code on: &lt;a href="https://github.com/SpokaneTech/SpokaneTechWeb" rel="noopener noreferrer"&gt;github&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Optimizing Your Django Docker Images: The Power of Multi-Stage Builds&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;At SpokaneTech.org, we're always looking for ways to improve our development and deployment processes. A cornerstone of modern web application deployment is containerization, and for that, we rely on Docker. Docker allows us to package our Django application with all of its dependencies into a single, portable container.&lt;/p&gt;

&lt;p&gt;However, as applications grow, so can the complexity and size of their Docker images. A bloated Docker image can lead to slower deployment times, increased storage costs, and a larger attack surface. Today, we'll explore how we optimized our Docker workflow for our Django project by transitioning from a single-stage to a multi-stage Dockerfile.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The Starting Point: A Single-Stage Dockerfile&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;When you first start Dockerizing a project, you'll likely begin with a single-stage Dockerfile. It's straightforward and gets the job done. Here’s what our initial Dockerfile looked like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM python:3.12-slim

# Set environment variables
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1

# Set the working directory
WORKDIR /app

# Install system dependencies
RUN apt-get update &amp;amp;&amp;amp; apt-get install -y \
    libglib2.0-0 \
    libnss3 \
    libx11-xcb1 \
    libxcomposite1 \
    libxrandr2 \
    libxdamage1 \
    libgbm1 \
    libasound2 \
    libpangocairo-1.0-0 \
    netcat-openbsd \
    &amp;amp;&amp;amp; rm -rf /var/lib/apt/lists/*

COPY pyproject.toml /app/
COPY src/django_project /app/

RUN chmod +x /app/entrypoint.sh
RUN pip install --upgrade pip pip-tools
RUN pip install .[docker]
RUN playwright install --with-deps

# Expose the port that the app runs on
EXPOSE 8000

CMD ["gunicorn", "core.wsgi:application", "--bind", "0.0.0.0:8000", "--workers", "3"]

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This Dockerfile works, but it has some significant drawbacks. It installs all our Python dependencies, including development and testing libraries like pip-tools and playwright, directly into the final image. It also includes the system dependencies required to install Playwright's browsers, not just run them. The result is a larger-than-necessary image that contains tools we don't need in a production environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The Solution: Embracing Multi-Stage Builds&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;A multi-stage build is a powerful feature in Docker that allows you to use multiple FROM statements in a single Dockerfile. Each FROM instruction begins a new "stage" of the build. You can selectively copy artifacts—like compiled code or installed packages—from one stage to another, leaving behind everything you don't need.&lt;/p&gt;

&lt;p&gt;Let's look at our new and improved multi-stage Dockerfile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# ====================
# Stage 1: Builder
# ====================
FROM python:3.12-slim AS builder

WORKDIR /app

# Install system dependencies needed for building and running Chromium
RUN apt-get update &amp;amp;&amp;amp; apt-get install -y --no-install-recommends \
    libglib2.0-0 libnss3 libx11-xcb1 libxcomposite1 libxrandr2 \
    libxdamage1 libgbm1 libasound2 libpangocairo-1.0-0 \
    libatk-bridge2.0-0 libgtk-3-0 fonts-liberation libxshmfence1 \
    libxcb1 xdg-utils netcat-openbsd \
    &amp;amp;&amp;amp; apt-get clean &amp;amp;&amp;amp; rm -rf /var/lib/apt/lists/*

# Install uv (fast dependency resolver) and create virtualenv
RUN pip install --no-cache-dir uv \
    &amp;amp;&amp;amp; python -m venv /venv

ENV PATH="/venv/bin:$PATH"

# Copy project dependency definition
COPY pyproject.toml /app/

# Install Python dependencies (including Playwright and its deps)
RUN uv pip install .[docker] --prerelease=allow \
    &amp;amp;&amp;amp; playwright install chromium --with-deps

# ====================
# Stage 2: Runtime
# ====================
FROM python:3.12-slim

WORKDIR /app

# Install only necessary system dependencies for Chromium to run
RUN apt-get update &amp;amp;&amp;amp; apt-get install -y --no-install-recommends \
    libglib2.0-0 libnss3 libx11-xcb1 libxcomposite1 libxrandr2 \
    libxdamage1 libgbm1 libasound2 libpangocairo-1.0-0 \
    libatk-bridge2.0-0 libgtk-3-0 fonts-liberation libxshmfence1 \
    libxcb1 xdg-utils netcat-openbsd \
    &amp;amp;&amp;amp; apt-get clean &amp;amp;&amp;amp; rm -rf /var/lib/apt/lists/*

# Environment settings
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
ENV PATH="/venv/bin:$PATH"
ENV PLAYWRIGHT_BROWSERS_PATH=/root/.cache/ms-playwright

# Copy the virtual environment and browser binaries from the builder
COPY --from=builder /venv /venv
COPY --from=builder /root/.cache/ms-playwright /root/.cache/ms-playwright

# Copy Django app code
COPY src/django_project /app/

# Make sure entrypoint script is executable
RUN chmod +x /app/entrypoint.sh

# Expose app port
EXPOSE 8000

# Entrypoint handles DB wait, migrations, and server start
CMD ["./entrypoint.sh"]

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This Dockerfile is split into two distinct stages:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;The builder Stage: This first stage is where the heavy lifting happens. We install all the necessary build tools and system libraries. We use the speedy uv installer to create a virtual environment and install all our Python dependencies. We also download and set up the Playwright browser binaries. This stage is a complete development and build environment.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The runtime Stage: This is the stage that will become our final production image. It starts from a fresh, clean python:3.12-slim image. Notice what we copy from the builder stage using the --from=builder flag:&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- The entire Python virtual environment (/venv).

- The installed Playwright browser (/root/.cache/ms-playwright).
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We then copy in our application code. The final image contains only our Python environment, browser binaries, our code, and the minimal system dependencies needed to run them—nothing else.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The Key Benefits of Going Multi-Stage&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;So, what have we gained from this approach?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Dramatically Smaller Image Sizes: By excluding build tools, -dev packages, and other intermediate files, the final image is significantly smaller. This means faster push/pull times from our container registry, quicker deployments, and reduced storage costs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enhanced Security: A smaller image has a smaller attack surface. By not shipping our build tools, compilers, or development dependencies, we remove potential vulnerabilities that could be exploited in a production environment.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Improved Caching and Faster Builds: Docker is smart about caching layers. In our multi-stage setup, if we only change our application code (src/django_project), Docker can use the cached builder stage without re-installing all the dependencies, speeding up subsequent builds.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cleaner and More Maintainable Dockerfiles: Separating the build and runtime concerns makes the Dockerfile more organized and easier to understand. It clearly communicates what is needed to build the application versus what is needed to run it.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Conclusion&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;While a single-stage Dockerfile is a great way to get started, adopting a multi-stage build process is a crucial step toward creating professional, production-ready container images. For SpokaneTech.org, this switch has resulted in a more efficient, secure, and streamlined deployment pipeline for our Django application. If you're not already using multi-stage builds, we highly recommend giving them a try!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>python</category>
      <category>docker</category>
    </item>
    <item>
      <title>Building Spokane Tech: Part 8</title>
      <dc:creator>David</dc:creator>
      <pubDate>Fri, 22 Aug 2025 22:42:14 +0000</pubDate>
      <link>https://dev.to/spokanetech/building-spokane-tech-part-8-5h0e</link>
      <guid>https://dev.to/spokanetech/building-spokane-tech-part-8-5h0e</guid>
      <description>&lt;h1&gt;
  
  
  &lt;strong&gt;Building Spokane Tech: Part 8&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;Welcome to part 8 of the "Building Spokane Tech" series! In this article, we'll discuss adding Docker and Docker Compose for running components of our service in containers.&lt;/p&gt;

&lt;p&gt;Containerization has become an essential tool for modern web development, and Docker is at the forefront of this revolution. When developing a Django-based web application like ours, using Docker ensures consistency across development and deployed environments. By leveraging Docker Compose, we can efficiently manage multiple services required by our application.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;See the live site at: &lt;a href="https://www.spokanetech.org" rel="noopener noreferrer"&gt;https://www.spokanetech.org&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;See the latest code on: &lt;a href="https://github.com/SpokaneTech/SpokaneTechWeb" rel="noopener noreferrer"&gt;github&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Docker Compose&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Docker Compose is a tool that allows you to define and manage multi-container Docker applications using a simple YAML file (docker-compose.yaml). It enables developers to run interconnected services, such as a web application, database, and message broker, with a single command. The  Docker Compose basic concepts include:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Key Docker Compose Configuration Options&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;version:&lt;/strong&gt; Defines the Compose file format version. In our case, we use "3.9", which is one of the latest stable versions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;services:&lt;/strong&gt; Lists all the containers that make up the application. Each service runs in its own container.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Service Configuration Keys&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;image:&lt;/strong&gt; Specifies the Docker image to use for the container. If the image is not found locally, Docker will pull it from a registry like Docker Hub.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;build:&lt;/strong&gt; Defines how to build the image from a Dockerfile. It usually includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;context: The directory containing the Dockerfile.&lt;/li&gt;
&lt;li&gt;dockerfile: The path to the specific Dockerfile used to build the image.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;container_name:&lt;/strong&gt; Gives a custom name to the container instead of a randomly generated one.&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;command:&lt;/strong&gt; Overrides the default command specified in the Dockerfile, allowing you to run specific commands when the container starts.&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;env_file:&lt;/strong&gt; Loads environment variables from an external .env file.&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;ports:&lt;/strong&gt; Maps ports between the container and the host.&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;depends_on:&lt;/strong&gt; Specifies service dependencies. A container will not start until its dependencies are up and running.&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Volumes&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Volumes store persistent data outside the container filesystem, ensuring data is not lost when containers are restarted or removed.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Our Services&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Let's review the components in our system, each of these will be a service in our docker-compose.yaml file.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Django (Web Application) – The core application running on Gunicorn or the Django development server&lt;/li&gt;
&lt;li&gt;PostgreSQL (Database) – Stores application data&lt;/li&gt;
&lt;li&gt;Redis (Message Broker) – Used by Celery for task queuing&lt;/li&gt;
&lt;li&gt;Celery Worker – Executes asynchronous tasks&lt;/li&gt;
&lt;li&gt;Celery Beat – Handles scheduled tasks&lt;/li&gt;
&lt;li&gt;Celery Flower – Provides a web UI for monitoring Celery tasks&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Our docker-compose.yaml file&lt;/strong&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;version: '3.9'

services:
  django:
    image: spokanetech-django:latest
    container_name: django
    env_file:
      - .env.compose
    build:
      context: ../..
      dockerfile: src/docker/Dockerfile
    command: ./entrypoint.sh
    ports:
      - "8080:8000"
    depends_on:
      - db
      - redis

  db:
    image: postgres:17
    container_name: postgres_db
    volumes:
      - postgres_data:/var/lib/postgresql/data
    ports:
      - "5432:5432"
    env_file:
      - .env.compose

  redis:
    image: redis:7.2-alpine
    container_name: redis
    restart: unless-stopped
    ports:
      - "6379:6379"

  worker:
    image: spokanetech-django:latest
    container_name: worker
    env_file:
      - .env.compose
    build:
      context: ../..
      dockerfile: src/docker/Dockerfile
    command: celery -A core worker -l info
    depends_on:
      - redis
      - db

  beat:
    image: spokanetech-django:latest
    container_name: beat
    env_file:
      - .env.compose
    build:
      context: ../..
      dockerfile: src/docker/Dockerfile
    command: celery -A core beat -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler
    depends_on:
      - redis
      - db

  flower:
    image: spokanetech-django:latest
    container_name: flower
    env_file:
      - .env.compose
    command: ["celery", "-A", "core", "--config=flowerconfig.py", "flower"]
    ports:
      - "5555:5555"
    depends_on:
      - redis
      - db

volumes:
  postgres_data:
  static_volume:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;strong&gt;Running the Application&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Docker Compose provides several commands to manage services. Here are the basics:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Building containers&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To build the containers run:&lt;br&gt;
&lt;br&gt;
 &lt;code&gt;docker-compose build&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;This builds images for the services defined in docker-compose.yaml using the specified Dockerfile. If an image already exists, it will only rebuild if changes are detected.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Starting containers&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To start the containers run:&lt;br&gt;
&lt;br&gt;
 &lt;code&gt;docker-compose up&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;This starts all services defined in docker-compose.yaml. It also automatically builds missing images if they are not found.&lt;/p&gt;

&lt;p&gt;To run the containers in detached mode use:&lt;br&gt;
&lt;br&gt;
 &lt;code&gt;docker-compose up -d&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;This runs containers in the background and allows applications to run persistently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Stopping containers&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To stop the containers use:&lt;br&gt;
&lt;br&gt;
 &lt;code&gt;docker-compose down&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;This stops and removes all containers, networks, and volumes (if specified); it does not remove built images.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Rebuild and restart containers&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To build the container when running, use:&lt;br&gt;
&lt;br&gt;
 &lt;code&gt;docker-compose up --build&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;This rebuilds images before starting containers and ensures the latest changes in the Dockerfile are applied.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;em&gt;Accessing Services&lt;/em&gt;
&lt;/h2&gt;

&lt;p&gt;All of our components are available on localhost on various their applicable ports: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Django App: &lt;a href="http://localhost:8080" rel="noopener noreferrer"&gt;http://localhost:8080&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Celery Flower UI: &lt;a href="http://localhost:5555" rel="noopener noreferrer"&gt;http://localhost:5555&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;PostgreSQL: Connect via localhost:5432&lt;/li&gt;
&lt;li&gt;Redis: Available on localhost:6379&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>docker</category>
      <category>python</category>
    </item>
    <item>
      <title>Building Spokane Tech: Part 7</title>
      <dc:creator>David</dc:creator>
      <pubDate>Sun, 13 Jul 2025 21:32:55 +0000</pubDate>
      <link>https://dev.to/spokanetech/building-spokane-tech-part-7-1joa</link>
      <guid>https://dev.to/spokanetech/building-spokane-tech-part-7-1joa</guid>
      <description>&lt;h1&gt;
  
  
  &lt;strong&gt;Building Spokane Tech: Part 7&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;Welcome to part 7 of the "Building Spokane Tech" series! In this article, we'll explore integrating Celery for scheduling tasks, executing work asynchronously, and monitoring task statuses.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;See the live site at: &lt;a href="https://www.spokanetech.org" rel="noopener noreferrer"&gt;https://www.spokanetech.org&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;See the latest code on: &lt;a href="https://github.com/SpokaneTech/SpokaneTechWeb" rel="noopener noreferrer"&gt;github&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Django Tasks and Celery&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;In a Django project, tasks.py is typically used to define background tasks that are executed asynchronously. This is common in applications where long-running or scheduled operations (such as sending emails, processing files, or performing API calls) should not block the main request-response cycle.&lt;/p&gt;

&lt;p&gt;Celery is a distributed task queue that integrates well with Django to handle background jobs asynchronously. It allows you to schedule and execute tasks efficiently.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Creating Tasks&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;In our service we'll use tasks defined in the tasks.py file to perform data ingestion from eventbrite and meetup. We have a few different tasks defined, namely a 'parent' launcher task, and a separate 'child' task for performing the bulk of the work. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Parent Tasks&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The parent task approach allows us to enable some basic control over what work gets queued for execution. For example, to run event ingestion for eventbrite groups we can first get a list of groups from our database, then launch individual jobs for each. Each job can succeed or fail independently of other jobs, and we'll have separate outputs and results. &lt;/p&gt;

&lt;p&gt;Here's an example of a parent task:&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="nd"&gt;@shared_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time_limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;900&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_retries&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;web.launch_eventbrite_event_ingestion&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;launch_eventbrite_event_ingestion&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;parent task for ingesting future events for tech groups on Eventbrite&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;tech_group_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TechGroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;enabled&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;platform__name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Eventbrite&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;group&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;tech_group_list&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;job&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ingest_future_eventbrite_events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;s&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apply_async&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the code above, the&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
 is a queryset of groups that are enabled and on the eventbrite platform. The

 ```ingest_future_eventbrite_events```

 is the task to be executed, and

 ```job.apply_async()```

 is the code to put the job on the queue for a background worker to consume.


## **Scheduling Tasks**

***Django Celery Beat***

Django Celery Beat is an extension for Celery that enables scheduling periodic 
tasks in a cron-like manner using the Django Admin panel. It allows you to manage scheduled tasks dynamically without modifying code.

In our service we use this to automatically run the parent tasks at regularly scheduled intervals, for example, every eight hours.


## **Monitoring Tasks**

***Celery Flower***

Celery Flower is a real-time web-based monitoring tool for Celery. It provides insights into task execution, workers, queues, and performance. Key features include:

Dashboard Overview
- Displays task statistics (Succeeded, Failed, Pending, etc.)
- Shows real-time execution of Celery tasks

Workers Monitoring
- Lists all active workers
- Shows worker status, uptime, and queue load

Task Monitoring
- Provides details of each task (Arguments, Status, Runtime, etc.)
- Allows you to retry failed tasks

Queues &amp;amp; Broker Inspection
- Displays Celery queues and their task load
- Shows messages in Redis or RabbitMQ queues
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>webdev</category>
      <category>softwaredevelopment</category>
      <category>python</category>
      <category>django</category>
    </item>
    <item>
      <title>Building Spokane Tech: Part 6</title>
      <dc:creator>David</dc:creator>
      <pubDate>Thu, 10 Jul 2025 04:00:50 +0000</pubDate>
      <link>https://dev.to/spokanetech/building-spokane-tech-part-6-3jp1</link>
      <guid>https://dev.to/spokanetech/building-spokane-tech-part-6-3jp1</guid>
      <description>&lt;h1&gt;
  
  
  &lt;strong&gt;Building Spokane Tech: Part 6&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;Welcome to part 6 of the "Building Spokane Tech" series! In this article, we'll look at how to scrape group and event data from meetup.com and eventbrite.com and populate our database accordingly. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;See the live site at: &lt;a href="https://www.spokanetech.org" rel="noopener noreferrer"&gt;https://www.spokanetech.org&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;See the latest code on: &lt;a href="https://github.com/SpokaneTech/SpokaneTechWeb" rel="noopener noreferrer"&gt;github&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Source Data&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;There are a couple platforms that tech groups are currently utilizing, with the majority being on meetup.com and eventbrite.com. We'll focus on collecting group and event data from those two platforms and address data sourcing from additional platforms as future needs dictate. &lt;/p&gt;

&lt;p&gt;Getting data from these platforms from their sites into ours involves a couple steps, including capturing the data, parsing the data, and storing it in our database for use on the website.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Data Collection&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Data collection for eventbrite and meetup will vary as eventbrite includes an API we can utilize for free, but meetup only has API access available for a fee. Do to this, we'll be web scraping. &lt;/p&gt;

&lt;p&gt;The first step is to actually capture HTML from a page. In some cases this can be as simple as using the requests library like so:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;html_content = requests.get("www.some_url.com")&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;This works okay for some pages, but not of others, where a timeout occurs or the expected html content is not found. This could be due to rate limiting, bot detection, or other server side measures. To make web capture more reliable, we're utilizing Playwright, an automation library for controlling websites. Playwright creates a browser instance for a more real-life interaction with data on a website. It also provides some more advance tools like network waits, and detection of html elements. This provides a more reliable means of collecting data, but is slower and requires a number of heavier dependencies. &lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Data Parsing&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Once we've captured html content from a web page we'll need to parse out specific pieces of data. To accomplish this we're using BeautifulSoup, a Python library for pulling data out of HTML files.&lt;/p&gt;

&lt;p&gt;For events on meetup we perform two capture and parse steps, one to get the list of events from the group page where links to future events are listed, and another to open and capture data from the detail page of each individual event. &lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Code/Utilities&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Under the web app we have a utilities subdirectory with scrapper files for eventbrite and meetup. Utilities for additional platforms may be added in the future. There's also a html_utils file that contains code to capture html content with requests and playwright.&lt;br&gt;
The directory structure looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.
├── src
│   ├── django_project
│   │   ├── core
│   │   │   └── ...
│   │   ├── tests
│   │   │   └── ...
│   │   ├── web
│   │   │   ├── utilities
│   │   │   │     └── scrapers
│   │   │   │         ├── eventbrite.py
│   │   │   │         └── meetup.py
│   │   │   ├── html_utils.py
│   │   │   └── ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>webdev</category>
      <category>programming</category>
      <category>python</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>Building Spokane Tech: Part 5</title>
      <dc:creator>David</dc:creator>
      <pubDate>Sat, 05 Jul 2025 19:55:11 +0000</pubDate>
      <link>https://dev.to/spokanetech/building-spokane-tech-part-5-344l</link>
      <guid>https://dev.to/spokanetech/building-spokane-tech-part-5-344l</guid>
      <description>&lt;h1&gt;
  
  
  &lt;strong&gt;Building Spokane Tech: Part 5&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;Welcome to part 5 of the "Building Spokane Tech" series! In this article, we'll explore the django views and templates used to server web pages in the initial web app. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;See the live site at: &lt;a href="https://www.spokanetech.org" rel="noopener noreferrer"&gt;https://www.spokanetech.org&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;See the latest code on: &lt;a href="https://github.com/SpokaneTech/SpokaneTechWeb" rel="noopener noreferrer"&gt;github&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Pages&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The initial release of spokanetech.org includes a small set of pages, including the landing page, an about page, a calendar page, and event and group pages. Here is a brief description of each page:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Index:&lt;/strong&gt; Home page of spokanetech.org&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;About:&lt;/strong&gt; Informational page for spokanetech.org&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Calendar:&lt;/strong&gt; Monthly Calendar listing all events per month&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Group List:&lt;/strong&gt; Page listing all tech groups&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Event List:&lt;/strong&gt; Page listing all upcoming events&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Group Detail:&lt;/strong&gt; Page detailing a specific tech group&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Event Detail:&lt;/strong&gt; Page detailing a specific event&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Views&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The views are designed to support both htmx and non-htmx requests. A different template will be used accordingly when the &lt;em&gt;"template_name"&lt;/em&gt; and &lt;em&gt;"htmx_template_name"&lt;/em&gt; parameters are provided. Selection of the template is handled by the base class.&lt;/p&gt;

&lt;p&gt;For our main entities we have a detail, list, and modal views to present data as applicable on web pages. Below is a snapshot of the tech group views. See &lt;a href="https://github.com/SpokaneTech/SpokaneTechWeb/blob/main/src/django_project/web/views.py" rel="noopener noreferrer"&gt;github&lt;/a&gt; for the latest and complete code.&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TechGroupView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HtmxOptionDetailView&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Render detail page for a TechGroup instance&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TechGroup&lt;/span&gt;
    &lt;span class="n"&gt;htmx_template_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;web/partials/detail/group.htm&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;template_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;web/full/detail/group.html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TechGroupsView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HtmxOptionMultiFilterView&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Render a list of TechGroup instances&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;htmx_list_template_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;web/partials/list/groups.htm&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;htmx_list_wrapper_template_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;web/partials/list/wrapper_list.htm&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;htmx_template_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;web/partials/marquee/groups.htm&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;queryset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TechGroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;enabled&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;template_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;web/full/list/groups.html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TechGroupModalView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ModelDetailBootstrapModalView&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Render Bootstrap 5 modal displaying get details of a TechGroup instance&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;modal_button_submit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;modal_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;modal-lg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;modal_template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;web/partials/modal/group_information.htm&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;modal_title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Group Info&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TechGroup&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;strong&gt;Templates&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Templates are split into to sections, full and partials. Templates in the full subdirectory hold the template that extend the django base template and are used when the request to the page is not htmx. Templates in the partials subdirectory are used by htmx requests. We use a filename convention where full pages have the .html extension and partials have the .htm extension.&lt;/p&gt;

&lt;p&gt;Here is the directory structure of the templates directories:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.
├── src
│   ├── django_project
│   │   ├── core
│   │   │   └── templates
│   │   │       └── base.htm
│   │   ├── web
│   │   │   └── templates
│   │   │       └── web
│   │   │           ├──  full
│   │   │           │    ├──  custom
│   │   │           │    │    ├──  about.html
│   │   │           │    │    ├──  calendar.html
│   │   │           │    │    └──  index.html
│   │   │           │    ├──  detail
│   │   │           │    │    ├──  event.html
│   │   │           │    │    └──  group.html
│   │   │           │    └──  list
│   │   │           │         ├──  events.html
│   │   │           │         └──  groups.html
│   │   │           │
│   │   │           └──  partials
│   │   │                ├──  custom
│   │   │                │    ├──  about.htm
│   │   │                │    ├──  calendar.htm
│   │   │                │    └──  index.htm
│   │   │                ├──  detail
│   │   │                │    ├──  event.htm
│   │   │                │    └──  group.htm
│   │   │                ├──  list
│   │   │                │    ├──  events.htm
│   │   │                │    └──  groups.htm
│   │   │                ├──  marquee
│   │   │                │    ├──  events.htm
│   │   │                │    └──  groups.htm
│   │   │                └──  modal
│   │   │                     ├──  event.htm
│   │   │                     └──  group.htm
│   │   └── ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>webdev</category>
      <category>django</category>
      <category>html</category>
      <category>htmx</category>
    </item>
    <item>
      <title>Building Spokane Tech: Part 4</title>
      <dc:creator>David</dc:creator>
      <pubDate>Fri, 04 Jul 2025 01:29:14 +0000</pubDate>
      <link>https://dev.to/spokanetech/building-spokane-tech-part-4-1bk1</link>
      <guid>https://dev.to/spokanetech/building-spokane-tech-part-4-1bk1</guid>
      <description>&lt;h1&gt;
  
  
  &lt;strong&gt;Building Spokane Tech: Part 4&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;Welcome to part 4 of the "Building Spokane Tech" series! In this article, we look at the initial modeling for the web app. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;See the live site at: &lt;a href="https://www.spokanetech.org" rel="noopener noreferrer"&gt;https://www.spokanetech.org&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;See the latest code on: &lt;a href="https://github.com/SpokaneTech/SpokaneTechWeb" rel="noopener noreferrer"&gt;github&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Initial Django Models&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The initial release of spokanetech.org will include five models in the web application: Event, Link, SocialPlatform, Tag, and TechGroup. Let's breakdown the role of each.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Event:&lt;/strong&gt; A tech event hosted by a TechGroup&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Link:&lt;/strong&gt; A link associated with a TechGroup, such as the group home page&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SocialPlatform:&lt;/strong&gt; The platform hosting the event, such as Meetup or EventBright&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tag:&lt;/strong&gt; An attributes of a Event or a TechGroup&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TechGroup:&lt;/strong&gt; A local tech group that organizes events&lt;/p&gt;

&lt;p&gt;Each of these models use the HandyHelperBaseModel which includes created_at and updated_at datetime fields and a model manager that provides a number of useful methods.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Model Code&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Below is a snapshot of the model code. See &lt;a href="https://github.com/SpokaneTech/SpokaneTechWeb/blob/main/src/django_project/web/models.py" rel="noopener noreferrer"&gt;github&lt;/a&gt; for the latest and complete model code.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;&lt;em&gt;Event&lt;/em&gt;&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HandyHelperBaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;An event on a specific day and time&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;start_datetime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DateTimeField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;auto_now&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;auto_now_add&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;help_text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date and time the event starts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;end_datetime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DateTimeField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;auto_now&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;auto_now_add&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;help_text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date and time the event ends&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;location_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;blank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;help_text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name of location where this event is being hosted&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;location_address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;blank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;help_text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;address of location where this event is being hosted&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;map_link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;URLField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;blank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;help_text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;link to a map showing the location of the event&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;URLField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;blank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;help_text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;URL to the event details&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;social_platform_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;blank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;help_text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unique identifier provided by the social platform hosting the event&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;group&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TechGroup&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;blank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;on_delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SET_NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;tags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ManyToManyField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Tag&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;blank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ImageField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;upload_to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tech_events/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;blank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;&lt;em&gt;Link&lt;/em&gt;&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HandyHelperBaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;A link to a resource associated with a TechGroup or Event&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;blank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;blank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;URLField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;&lt;em&gt;SocialPlatform&lt;/em&gt;&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SocialPlatform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HandyHelperBaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;The social platform (such as Meetup) that hosts the group and events&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;unique&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;help_text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;service where this tech group is hosted&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;enabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BooleanField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;base_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;URLField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;help_text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;base url of provider&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;&lt;em&gt;Tag&lt;/em&gt;&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HandyHelperBaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;A Tag that describes attributes of a Event&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;unique&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;&lt;em&gt;TechGroup&lt;/em&gt;&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TechGroup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HandyHelperBaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;A group that organizes events&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;unique&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;enabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BooleanField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;platform&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SocialPlatform&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;on_delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CASCADE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;icon&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;blank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;help_text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Font Awesome CSS icon class(es) to represent the group.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;tags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ManyToManyField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Tag&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;blank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;links&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ManyToManyField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Link&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;blank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ImageField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;upload_to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;techgroups/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;blank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>webdev</category>
      <category>django</category>
      <category>programming</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>Building Spokane Tech: Part 3</title>
      <dc:creator>David</dc:creator>
      <pubDate>Thu, 03 Jul 2025 03:52:27 +0000</pubDate>
      <link>https://dev.to/spokanetech/building-spokane-tech-part-3-akk</link>
      <guid>https://dev.to/spokanetech/building-spokane-tech-part-3-akk</guid>
      <description>&lt;h1&gt;
  
  
  &lt;strong&gt;Building Spokane Tech: Part 3&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;Welcome to part 3 of the "Building Spokane Tech" series! In this article, we go through steps to get the web app running locally on your machine or environment.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;See the live site at: &lt;a href="https://www.spokanetech.org" rel="noopener noreferrer"&gt;https://www.spokanetech.org&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;See the latest code on: &lt;a href="https://github.com/SpokaneTech/SpokaneTechWeb" rel="noopener noreferrer"&gt;github&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Prerequisites&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;git installed on system&lt;/li&gt;
&lt;li&gt;python installed on system (3.10+ recommended)&lt;/li&gt;
&lt;li&gt;access to the SpokaneTechWeb github repo: &lt;a href="https://github.com/SpokaneTech/SpokaneTechWeb" rel="noopener noreferrer"&gt;https://github.com/SpokaneTech/SpokaneTechWeb&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Local Setup&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Cloning the Repo
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone git@github.com:SpokaneTech/SpokaneTechWeb.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  cd into the repo directory
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd SpokaneTechWeb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create a python virtual environment
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python -m venv venv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Activate the python virtual environment
&lt;/h3&gt;

&lt;p&gt;for linux, mac, or wsl:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;source venv/bin/activate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;for powershell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;venv\Scripts\activate&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Install the python dependencies
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip install .[dev]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;** mac users may need to quote the pip install like so:&lt;br&gt;
&lt;br&gt;
 &lt;code&gt;pip install '.[dev]'&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  Install playwright dependencies
&lt;/h3&gt;

&lt;p&gt;Playwright is used for scraping web data from meetup.com&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;playwright install --with-deps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create an .env.local file from the .env.template file and update contents as applicable
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cp src/envs/.env.template src/envs/.env.local
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  cd to the django_project directory
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd src/django_project
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create a local database by running django migrations
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python ./manage.py migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create a local admin user
&lt;/h3&gt;

&lt;p&gt;This command creates a superuser superuser in your database and adds the user to the admin group. The username is 'admin' and the password is 'admin'&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python ./manage.py add_superuser --group admin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Populate some local test data
&lt;/h3&gt;

&lt;p&gt;This command populates your local database with SocialPlatform and TechGroup data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python ./manage.py runscript initialize_data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you'd like to ingest some actual future events data from Eventbrite and Meetup, run this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python ./manage.py runscript ingest_events
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Start the local demo server
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python ./manage.py runserver
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Explore the site
&lt;/h3&gt;

&lt;p&gt;open a browser and navigate to &lt;a href="http://127.0.0.1:8000" rel="noopener noreferrer"&gt;http://127.0.0.1:8000&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;** you can stop the local demo server anytime via&lt;br&gt;
&lt;br&gt;
 &lt;code&gt;ctrl + c&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;** you can login to the django admin page (at &lt;a href="http://127.0.0.1:8000" rel="noopener noreferrer"&gt;http://127.0.0.1:8000&lt;/a&gt;) using admin/admin&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Enable Git Hooks (optional)&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  git config
&lt;/h3&gt;

&lt;p&gt;To enable pre-commit code quality checks, update the location of git hooks with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git config core.hooksPath .github/hooks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note: to make a commit with the precommit hooks temporarily disabled, run the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git commit --no-verify
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>softwareengineering</category>
      <category>webdev</category>
      <category>python</category>
      <category>git</category>
    </item>
    <item>
      <title>Building Spokane Tech: Part 2</title>
      <dc:creator>David</dc:creator>
      <pubDate>Wed, 02 Jul 2025 04:41:22 +0000</pubDate>
      <link>https://dev.to/spokanetech/building-spokane-tech-part-2-3j38</link>
      <guid>https://dev.to/spokanetech/building-spokane-tech-part-2-3j38</guid>
      <description>&lt;h1&gt;
  
  
  &lt;strong&gt;Building Spokane Tech: Part 2&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;Welcome to part 2 of the "Building Spokane Tech" series! In this article, we walk though the layout of the repository and detail what code lives where.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;See the live site at: &lt;a href="https://www.spokanetech.org" rel="noopener noreferrer"&gt;https://www.spokanetech.org&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;See the latest code on: &lt;a href="https://github.com/SpokaneTech/SpokaneTechWeb" rel="noopener noreferrer"&gt;github&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Repository Structure&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Below is an overview of the repository structure.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.
├── src
│   ├── django_project
│   │   ├── core
│   │   │   └── ...
│   │   ├── tests
│   │   │   ├── integration
│   │   │   │   └── ...
│   │   │   ├── regression
│   │   │   │   └── ...
│   │   │   └── unit
│   │   │       └── ...
│   │   ├── web
│   │   │   └── ...
│   │   └── manage.py
│   ├── docker
│   │   └── Dockerfile
│   └── envs
│       └── .env.template
├── .dockerignore
├── .gitignore
├── LICENSE
├── README.md
└── pyproject.toml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;strong&gt;File and Directory Descriptions&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;The Root Directory&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Our root directory is lean and clean, with only the standard files for a git repo, including a .gitignore, a LICENSE file, a README.md file, and a pyproject.toml. &lt;br&gt;
The pyproject.toml file contains our dependencies and tool configurations. This eliminates the need for additional configurations and requirements files. &lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;The src Directory&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;All of our actual code lives under the src directory. The are subdirectories for the django code, docker files, and our environment files.&lt;/p&gt;

&lt;h4&gt;
  
  
  django_project
&lt;/h4&gt;

&lt;p&gt;The django_project directory has &lt;strong&gt;&lt;em&gt;core&lt;/em&gt;&lt;/strong&gt;, the django project level directory, &lt;strong&gt;&lt;em&gt;web&lt;/em&gt;&lt;/strong&gt;, the django app directory, and &lt;strong&gt;&lt;em&gt;tests&lt;/em&gt;&lt;/strong&gt;, the location of all django unittests, with further subdirectories for test categories. Having tests at this level helps keep tests organized and allows for running tests by various scopes like for the entire project, for a specific django app, or specific area like models or apis.&lt;/p&gt;

&lt;h4&gt;
  
  
  docker
&lt;/h4&gt;

&lt;p&gt;The docker directory will have applicable docker related files such as the Dockerfile, docker-compose.yaml, and .dockerignore. &lt;/p&gt;

&lt;h4&gt;
  
  
  envs
&lt;/h4&gt;

&lt;p&gt;Any applicable .env files live here. There is a .env.template, .env.local, and other .env files as needed. Note, most .env files will not be checked into source control.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>softwaredevelopment</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Building Spokane Tech: Part 1</title>
      <dc:creator>David</dc:creator>
      <pubDate>Wed, 02 Jul 2025 04:01:07 +0000</pubDate>
      <link>https://dev.to/spokanetech/building-spokane-tech-part-1-2c2n</link>
      <guid>https://dev.to/spokanetech/building-spokane-tech-part-1-2c2n</guid>
      <description>&lt;h1&gt;
  
  
  &lt;strong&gt;Building Spokane Tech: Part 1&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;Welcome to the first part of the "Building Spokane Tech" series! In this article, we explore the tech stack, and design decisions.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;See the live site at: &lt;a href="https://www.spokanetech.org" rel="noopener noreferrer"&gt;https://www.spokanetech.org&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;See the latest code on: &lt;a href="https://github.com/SpokaneTech/SpokaneTechWeb" rel="noopener noreferrer"&gt;github&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Requirements&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;For the first phase of our project we want to identify all the tech related community groups in the Spokane area, gather data about them and ingest and present events they host in one location. To make this happen we'll need a couple things. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;web interface for displaying groups and events&lt;/li&gt;
&lt;li&gt;a database to store the groups, event, and associated information&lt;/li&gt;
&lt;li&gt;code that can gather data from applicable event sites&lt;/li&gt;
&lt;li&gt;a means to execute that code on a regular cadence &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Tech Stack&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Our tech stack will be comprised of the follow technologies (accompanied with a brief description of each):&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Python&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Role:&lt;/strong&gt; &lt;em&gt;Primary programming language&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Responsibility:&lt;/strong&gt; &lt;em&gt;Powers the application backend, providing a robust, readable, and flexible foundation for building web functionality and handling logic.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Django&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Role:&lt;/strong&gt; &lt;em&gt;Web framework&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Responsibility:&lt;/strong&gt; &lt;em&gt;Facilitates rapid development of secure and maintainable websites, handling URL routing, views, models, forms, and authentication. It integrates well with databases and supports REST API development.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Gunicorn&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Role:&lt;/strong&gt; &lt;em&gt;WSGI HTTP server&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Responsibility:&lt;/strong&gt; &lt;em&gt;Serves as the bridge between your Django application and the web server (e.g., Nginx). It efficiently handles multiple requests concurrently and scales well for production.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Redis&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Role:&lt;/strong&gt; &lt;em&gt;In-memory data store&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Responsibility:&lt;/strong&gt; &lt;em&gt;Used as a message broker for Celery tasks, caching, and real-time features like notifications or session management.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Postgres&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Role:&lt;/strong&gt; &lt;em&gt;Database&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Responsibility:&lt;/strong&gt; &lt;em&gt;Provides a reliable, scalable, and feature-rich relational database for storing application data, such as user information, product records, and transaction logs.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Celery&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Role:&lt;/strong&gt; &lt;em&gt;Task queue&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Responsibility:&lt;/strong&gt; &lt;em&gt;Manages asynchronous tasks (e.g., sending emails, processing files) by offloading time-consuming operations to background workers, improving responsiveness.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Celery Beat&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Role:&lt;/strong&gt; &lt;em&gt;Scheduler for Celery tasks&lt;/em&gt;&lt;br&gt;
Responsibility: &lt;em&gt;Executes periodic tasks by scheduling them at specific intervals (e.g., daily reports or regular database cleanup).&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;HTMX&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Role:&lt;/strong&gt; &lt;em&gt;Frontend interaction library&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Responsibility:&lt;/strong&gt; &lt;em&gt;Enhances user experience by enabling server-side rendered dynamic content updates without full page reloads. Simplifies AJAX requests, WebSockets, and DOM updates.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Bootstrap 5&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Role:&lt;/strong&gt; &lt;em&gt;CSS framework&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Responsibility:&lt;/strong&gt; &lt;em&gt;Simplifies frontend design with a responsive, mobile-first grid system and pre-designed components such as buttons, modals, and navigation bars. Speeds up development and ensures a consistent, modern UI.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>softwaredevelopment</category>
      <category>django</category>
      <category>python</category>
    </item>
    <item>
      <title>Building SpokaneTech.org</title>
      <dc:creator>David</dc:creator>
      <pubDate>Tue, 01 Jul 2025 04:52:15 +0000</pubDate>
      <link>https://dev.to/spokanetech/building-spokanetechorg-3h18</link>
      <guid>https://dev.to/spokanetech/building-spokanetechorg-3h18</guid>
      <description>&lt;h1&gt;
  
  
  &lt;strong&gt;About the Spokane Tech Project&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;The Spokane Tech website is a project for the community made by the community. The aim of the project is to deliver a community resource for all things tech in the Inland Northwest while providing an opportunity for contributes to gain real-world experience in a shared open source project.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Why are we doing this?&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Reason 1:
&lt;/h3&gt;

&lt;p&gt;There is a thriving tech community in Spokane, but many members of our community are disconnected. With multiple tech groups on different platforms, such as meetup and eventbright, there are often events of interest happening that many tech enthusiasts are not aware of. The intent is to have a single resource that includes local tech groups and the events they host.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reason 2:
&lt;/h3&gt;

&lt;p&gt;Many developers in our community, especially those earlier in their career, have skills and drive, but haven't had the opportunity to work on a project in a real professional environment. For example, a developer could have great knowledge in coding, but hasn't yet had the first professional job or participated in project with milestones, project planning, code reviews, etc. The Spokane Tech project aims to provide this and give contributes a project they can reference for career development, personal portfolios, interviews, etc. &lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Initial Vision&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;What our project (and webapp) becomes will ultimately be dictated by members of the project and will likely evolve over time. Below are some details of the initial vision.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase One:
&lt;/h3&gt;

&lt;p&gt;Have a web site that houses groups and events. Events may be manually or automatically added to our site. We will have views that list all the groups and events, as well as detail pages for each group and event. Ideally we'll also have a calendar view that can list all events and perhaps be filterable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase Two:
&lt;/h3&gt;

&lt;p&gt;Have event requests and suggestions capabilities. Here members can post a suggested events they want to give or have someone else give, and others can up/down vote the event (think reddit or stackoverflow). This can be used to prioritize events base on community interest. This can also serve as a living backlog of event ideas. Add labels to events, such as technical areas (frontend, scripting, ML, etc.) and topic levels (beginner/intermediate/etc.). With labels people can filter event based on interest and other criteria.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase Three:
&lt;/h3&gt;

&lt;p&gt;Build member profiles. With profiles, we can have some basic metrics on things like career level, geographic location, interested and expertise. This data can help provide viability into the overall tech presence in Spokane and help drive event topics and location. This could also be a future resource to make available to local businesses and the community for things like contract work, etc. (There has been some outside interest in this type of resource)&lt;/p&gt;

&lt;h3&gt;
  
  
  Future goals:
&lt;/h3&gt;

&lt;p&gt;The Spokane Tech project was started mostly by members of the Spokane Python User Group (SPUG), so naturally the first version of the website is based on python. In the future the project may be re-created in other languages/frameworks/etc. (such as Golang or Rust) as member interest dictates. This is intended to foster growth, knowledge-sharing, and exposure to different tech stacks and methodologies.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Interested in participating? Great! Read on...&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Here are a few things you can do to get started.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Go to the site and explore. It's live at &lt;a href="https://www.spokanetech.org" rel="noopener noreferrer"&gt;https://www.spokanetech.org&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Look through the open issues and find one that interests you (issues tagged "good first issue" could be great candidates) on &lt;a href="https://github.com/SpokaneTech/SpokaneTechWeb/issues" rel="noopener noreferrer"&gt;github&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Read our &lt;a href="https://spokanetech.github.io/blog/building-spokane-tech/intro/" rel="noopener noreferrer"&gt;blog&lt;/a&gt; to learn more about the project, follow development and design decisions, and step through the process of building the site. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Clone the repo to you machine and run locally, explore the code, break things, fix things, have fun. Step by step instructions are in the CONTRIBUTION doc on &lt;a href="https://github.com/SpokaneTech/SpokaneTechWeb/blob/main/.github/CONTRIBUTING.md" rel="noopener noreferrer"&gt;github&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Have a feature idea or found a bug? Create an issue on &lt;a href="https://github.com/SpokaneTech/SpokaneTechWeb/issues" rel="noopener noreferrer"&gt;github&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Need more help or direction?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Join the &lt;a href="https://discord.com/channels/1087885800311632043/1175132957959266347" rel="noopener noreferrer"&gt;Discord channel&lt;/a&gt; (best option)&lt;br&gt;&lt;br&gt;
or post a question on &lt;a href="https://github.com/SpokaneTech/SpokaneTechWeb/issues/new?template=question.yaml" rel="noopener noreferrer"&gt;github&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;New to python, django, git, webdev? Reach out in the Discord channel and suggest a virtual meet. We'll schedule these on occasion, or as interest dictates. This can be used as q&amp;amp;a sessions, code paring, shared code reviews, or just follow along as a member works on an issue.&lt;/p&gt;

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