Our Blog

Bringing Blockchain to Project Management: A Developer’s Guide with Solidity & Python
Part 4
Wiring Blockchain with Django
Bringing Blockchain to Project Management: A Developer’s Guide with Solidity & Python

Bringing Blockchain to Project Management: A Developer’s Guide with Solidity & Python

Part 4 - Wiring Blockchain with Django

Part 1 | Bringing Blockchain to Project Management | Introduction : Read here

Part 2 | Bringing Blockchain to Project Management | Setting up Django Application : Read here

Part 3 | Bringing Blockchain to Project Management | Starting Smart Contract Development : Read here

This multi-part blog series explores the creation of a smart contract using the Ethereum network, Solidity, and the Python Django framework. The objective is to provide an accessible introduction to smart contracts through a practical, real-world example.

In this fourth episode, we’ll now create simple smart contracts for the Project and Sprint models using Solidity, compile them with Remix, and connect them to our Django app. Then, we’ll use Ganache Local chain to test saving data to the blockchain when new records are created.

In the end, we will test it by creating a sample project & sprint and verifying that it is deployed on our local ganache block-chain.

Basics: Solidity Data Types

Before we jump into creating smart contracts, we need to understand the basic data types in Solidity.

1. uint (Unsigned Integer)

A non-negative integer that can store whole numbers starting from 0. uint age = 30; // set function getAge() public view returns (uint) { return age; // retrieve }

2. int (Signed Integer)

Stores whole numbers that can be both negative and positive. int temperature = -5; function getTemp() public view returns (int) { return temperature; }

3. string

Stores a sequence of characters (text). string public name = "Alice"; function getName() public view returns (string memory) { return name; }

4. bool (Boolean)

Holds a true or false value. bool public isActive = true; function getStatus() public view returns (bool) { return isActive; }

5. address

Holds a 20-byte Ethereum address. address public user = 0xAb...123; function getUser() public view returns (address) { return user; }

6. bytes / bytes1 to bytes32

Stores fixed-size or dynamic raw byte data. bytes32 public hash = keccak256(abi.encodePacked("data")); function getHash() public view returns (bytes32) { return hash; }

7. enum

Custom type representing a group of named constant values. enum Status { Pending, Shipped, Delivered } Status public orderStatus = Status.Pending; function getStatus() public view returns (Status) { return orderStatus; }

8. struct

Custom type to group related variables under one name. struct Person { string name; uint age; } Person public p = Person("Ali", 25); function getPerson() public view returns (string memory, uint) { return (p.name, p.age); }

9. array

Stores a list of elements of the same type, either fixed or dynamic. uint[] public numbers = [1, 2, 3]; function getNumber(uint index) public view returns (uint) { return numbers[index]; }

10. mapping

Key-value store, like a dictionary. mapping(address => uint) public balances; function setBalance(address user, uint value) public { balances[user] = value; } function getBalance(address user) public view returns (uint) { return balances[user]; }

Creating Smart Contracts for Project and Sprint Entities | Prequisites

Before we jump into the development, we need to take care of two Pre-requisite tasks:

1: First of all let’s create a folder called contracts as shown: mkdir <DJANGO-ROOT-FOLDER>/contracts DJANGO-ROOT-FOLDER is the folder that contains the manage.py file.

2: We will also need to create .env file to store sensitive information. Please create it in the <DJANGO-ROOT-FOLDER>. GANACHE_URL=http://x.x.x.x:8545 GANACHE_ACCOUNT=0xYourGanacheAccountAddress GANACHE_PRIVATE_KEY=0xYourGanachePrivateKey

Creating Smart Contracts for Project Entity | Key Steps

Step #1: Create the Struct to match Django’s Project Model

In Solidity, we use a struct to define a custom data type. This is equivalent to defining a "record" or "row" in Django. struct Project { uint id; string projectName; uint customerId; string projectDescription; uint256 projectStart; uint256 projectEnd; uint256 projectActualStart; uint256 projectActualEnd; bool completed; uint projectManagerId; uint256 createdOn; uint256 updatedAt; }

Step #2: Store Projects in a Mapping

To store all projects on-chain, we use a mapping, which is Solidity’s way of storing key-value pairs (like a Python dictionary). mapping(uint => Project) public projects; uint public projectCount = 0;

Step #3: Add a Function to Create Projects

Here we define a createProject() function. It takes the same fields as the struct, saves them in the projects mapping, and emits an event. function createProject( string memory projectName, uint customerId, string memory projectDescription, uint256 projectStart, uint256 projectEnd, uint256 projectActualStart, uint256 projectActualEnd, bool completed, uint projectManagerId, uint256 createdOn, uint256 updatedAt ) public { projectCount++; projects[projectCount] = Project(...); emit ProjectCreated(...); }

Step #4: Emit Events for Transparency

We emit a ProjectCreated event so external applications (like a frontend or Django app) can listen for new records on-chain. event ProjectCreated( uint id, string projectName, uint customerId, string projectDescription, uint256 projectStart, uint256 projectEnd, uint256 projectActualStart, uint256 projectActualEnd, bool completed, uint projectManagerId, uint256 createdOn, uint256 updatedAt );

Step #5: Complete Code for Project Contract

Here is the complete code is shown below. Let’s store it in a file called ProjectContract.sol inside <DJANGO-ROOT-FOLDER>/contracts // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract ProjectContract { struct Project { uint id; string projectName; uint customerId; string projectDescription; uint256 projectStart; uint256 projectEnd; uint256 projectActualStart; uint256 projectActualEnd; bool completed; uint projectManagerId; uint256 createdOn; uint256 updatedAt; } uint public projectCount = 0; mapping(uint => Project) public projects; event ProjectCreated( uint id, string projectName, uint customerId, string projectDescription, uint256 projectStart, uint256 projectEnd, uint256 projectActualStart, uint256 projectActualEnd, bool completed, uint projectManagerId, uint256 createdOn, uint256 updatedAt ); function createProject( string memory projectName, uint customerId, string memory projectDescription, uint256 projectStart, uint256 projectEnd, uint256 projectActualStart, uint256 projectActualEnd, bool completed, uint projectManagerId, uint256 createdOn, uint256 updatedAt ) public { projectCount++; projects[projectCount] = Project( projectCount, projectName, customerId, projectDescription, projectStart, projectEnd, projectActualStart, projectActualEnd, completed, projectManagerId, createdOn, updatedAt ); emit ProjectCreated( projectCount, projectName, customerId, projectDescription, projectStart, projectEnd, projectActualStart, projectActualEnd, completed, projectManagerId, createdOn, updatedAt ); } }

Creating Smart Contracts for Sprint Entity

Using the above mechanism, we will create a contract for the Sprint entity. The final code is given in the following section. Let’s call this file SprintContract.sol and we should store it in <DJANGO-ROOT-FOLDER>/contracts. // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract SprintContract { struct Sprint { uint id; uint projectId; uint customerId; uint projectManagerId; uint256 sprintStart; uint256 sprintEnd; string sprintDescription; uint estimatedHours; uint totalItems; uint totalCompleted; uint256 createdOn; uint256 updatedAt; } uint public sprintCount = 0; mapping(uint => Sprint) public sprints; event SprintCreated( uint id, uint projectId, uint customerId, uint projectManagerId, uint256 sprintStart, uint256 sprintEnd, string sprintDescription, uint estimatedHours, uint totalItems, uint totalCompleted, uint256 createdOn, uint256 updatedAt ); function createSprint( uint projectId, uint customerId, uint projectManagerId, uint256 sprintStart, uint256 sprintEnd, string memory sprintDescription, uint estimatedHours, uint totalItems, uint totalCompleted, uint256 createdOn, uint256 updatedAt ) public { sprintCount++; sprints[sprintCount] = Sprint( sprintCount, projectId, customerId, projectManagerId, sprintStart, sprintEnd, sprintDescription, estimatedHours, totalItems, totalCompleted, createdOn, updatedAt ); emit SprintCreated( sprintCount, projectId, customerId, projectManagerId, sprintStart, sprintEnd, sprintDescription, estimatedHours, totalItems, totalCompleted, createdOn, updatedAt ); } }

Compile the Contracts

1. Load Remix UI in your favorite browser by visiting: Remix UI

2. Open our Project Contract file - ProjectContract.sol in Remix Remix UI

3. Compile the newly created contract Compile the contract

4. You will see that Compilation Successful method. Compilation Successful

5. Now go to Compilation screen and click ‘Compilation Details’ button. Compilation Details

6. Click on ‘ABI’ button to copy ABI to clipboard Click on Copy ABI button

7. Now store this as ProjectContract.json inside <DJANGO-ROOT-FOLDER>/contracts

8. Similarly, click on ‘Bytecode’ button to copy Bytecode to clipboard Click on Copy ByteCode button

9. Store bytecode as ‘ProjectContract.bytecode’ inside <DJANGO-ROOT-FOLDER>/contracts

Similarly, repeat the above process for SprintContract as well. Please store Sprint's ABI in SprintContract.json and Sprint's Bytecode in SprintContract.bytecode.

Deploy the Contracts

For the purpose of this blog, let’s deploy both Project and Sprint contracts using Remix UI. Please refer to the previous blog (link for detailed instructions) on how to deploy a contract using Remix.

Once a contract has been deployed, you will need to copy its address from Remix’s UI as shown in the following screenshot: Deployed Contract Repeat the process for Sprint contract as well. Make sure you keep the copied address(es) in a safe place. Let’s store them in our .env file.

Note, if you stop the running ganache network. You will lose the deployed contract. Redeployment will result in a new address.

Creating Project Entity Django Blockchain Integration Code

Now let’s create a Django file in the <DJANGO-ROOT-FOLDER</contracts called project_contract.py. This code consists of 4 logical blocks.

Block 1: Setup & Environment Loading

Loads necessary libraries and pulls values (like GANACHE_URL, account, private key, contract address) from a .env file to avoid hardcoding sensitive data. import json import os from web3 import Web3 from django.conf import settings from dotenv import load_dotenv load_dotenv() # Load environment variables from .env file ganache_url = os.environ["GANACHE_URL"] account = os.environ["GANACHE_ACCOUNT"] private_key = os.environ["GANACHE_PRIVATE_KEY"] project_contract_address = os.environ["PROJECT_CONTRACT_ADDRESS"]

Block 2: Blockchain Connection & Contract Initialization

Connects to a local Ethereum test network (Ganache) and prepares to interact with a deployed smart contract using its ABI and address. w3 = Web3(Web3.HTTPProvider(ganache_url)) # Connect to local Ganache abi_path = os.path.join(settings.BASE_DIR, 'contracts', 'ProjectContract.json') with open(abi_path) as f: abi = json.load(f) # Load ABI (Application Binary Interface) contract_address = Web3.to_checksum_address(project_contract_address) contract = w3.eth.contract(address=contract_address, abi=abi)

Block 3: Main Function to Push Project Data to Blockchain

Defines a function that takes a project object, maps its fields to the smart contract’s createProject method, and builds a transaction. def push_project_to_chain(project): tx = contract.functions.createProject( project.project_name, project.customer_id, project.project_description, int(project.project_start.timestamp()), int(project.project_end.timestamp()), int(project.project_actual_start.timestamp()), int(project.project_actual_end.timestamp()), project.completed, project.project_manager_id, int(project.created_on.timestamp()), int(project.updated_at.timestamp()) ).build_transaction({ 'from': account, 'nonce': w3.eth.get_transaction_count(account), 'gas': 300000, 'gasPrice': w3.to_wei('1', 'gwei') })

Block 4: Sign, Send Transaction & Return Hash

Signs the built transaction with the private key, sends it to the blockchain, and returns the transaction hash for tracking. signed_tx = w3.eth.account.sign_transaction(tx, private_key) print(signed_tx) tx_hash = w3.eth.send_raw_transaction(signed_tx.raw_transaction) return w3.to_hex(tx_hash)

Complete Code for project_contract.py

Here is the complete code for Django Integration code for BlockChain. import json import os from web3 import Web3 from django.conf import settings from dotenv import load_dotenv # Load environment variables from .env file load_dotenv() # Use environment variables ganache_url = os.environ["GANACHE_URL"] account = os.environ["GANACHE_ACCOUNT"] private_key = os.environ["GANACHE_PRIVATE_KEY"] project_contract_address = os.environ["PROJECT_CONTRACT_ADDRESS"] # Connect to Ganache w3 = Web3(Web3.HTTPProvider(ganache_url)) # Connect to local Ganache w3 = Web3(Web3.HTTPProvider(ganache_url)) # Load ABI abi_path = os.path.join(settings.BASE_DIR,'contracts', 'ProjectContract.json') with open(abi_path) as f: abi = json.load(f) # Deployed contract address from Remix (Ganache network) contract_address = Web3.to_checksum_address(project_contract_address) contract = w3.eth.contract(address=contract_address, abi=abi) # Main function to push data def push_project_to_chain(project): tx = contract.functions.createProject( project.project_name, project.customer_id, project.project_description, int(project.project_start.timestamp()), int(project.project_end.timestamp()), int(project.project_actual_start.timestamp()), int(project.project_actual_end.timestamp()), project.completed, project.project_manager_id, int(project.created_on.timestamp()), int(project.updated_at.timestamp()) ).build_transaction({ 'from': account, 'nonce': w3.eth.get_transaction_count(account), 'gas': 300000, 'gasPrice': w3.to_wei('1', 'gwei') }) signed_tx = w3.eth.account.sign_transaction(tx, private_key) print(signed_tx) tx_hash = w3.eth.send_raw_transaction(signed_tx.raw_transaction) return w3.to_hex(tx_hash)

Changes in the Models.py

We need to modify Project class in models.py. We need to add the followiung code. That will push information about the project to Blockchain when new project is created. def save(self, *args, **kwargs): super().save(*args, **kwargs) tx_hash = push_project_to_chain(self) print(f"Pushed to blockchain: {tx_hash}")

Changes in the Views.py

Update method for proect_create. Replace it with the following code so that it explicitly calls save method of models.py (created in the previous section). from contracts.project_contract import push_project_to_chain def project_create(request): if request.method == 'POST': form = ProjectForm(request.POST) if form.is_valid(): project = form.save() # This saves and returns the instance try: tx_hash = push_project_to_chain(project) print(f"✅ Project pushed to blockchain: {tx_hash}") except Exception as e: print(f"⚠️ Blockchain push failed: {e}") return redirect('project_list') else: form = ProjectForm() return render(request, 'blockchain_sprint_manager/project_form.html', {'form': form})

Creating Sprint Entity Django Blockchain Integration Code

Now let’s create a Django file in the <DJANGO-ROOT-FOLDER</contracts called sprint_contract.py. The complete code is given in the following: import os import json from web3 import Web3 from dotenv import load_dotenv from pathlib import Path from django.conf import settings # Load environment variables env_path = Path(__file__).resolve().parent.parent / ".env" load_dotenv(dotenv_path=env_path) # Load from .env ganache_url = os.environ["GANACHE_URL"] account = os.environ["GANACHE_ACCOUNT"] private_key = os.environ["GANACHE_PRIVATE_KEY"] contract_address = Web3.to_checksum_address(os.environ["SPRINT_CONTRACT_ADDRESS"]) # Connect to Ganache w3 = Web3(Web3.HTTPProvider(ganache_url)) assert w3.is_connected(), "Web3 connection to Ganache failed" # Load ABI abi_path = os.path.join(settings.BASE_DIR, 'contracts', 'SprintContract.json') with open(abi_path) as f: abi = json.load(f) # Load the deployed contract contract = w3.eth.contract(address=contract_address, abi=abi) # Function to send Sprint data def push_sprint_to_chain(sprint): tx = contract.functions.createSprint( sprint.project_id, sprint.customer_id, sprint.project_manager_id, int(sprint.sprint_start.timestamp()), int(sprint.sprint_end.timestamp()), sprint.sprint_description, sprint.estimated_hours, sprint.total_items, sprint.total_completed, int(sprint.created_on.timestamp()), int(sprint.updated_at.timestamp()) ).build_transaction({ 'from': account, 'nonce': w3.eth.get_transaction_count(account), 'gas': 300000, 'gasPrice': w3.to_wei('1', 'gwei') }) signed_tx = w3.eth.account.sign_transaction(tx, private_key) tx_hash = w3.eth.send_raw_transaction(signed_tx.raw_transaction) return w3.to_hex(tx_hash)

Changes in the Models.py

We need to modify Sprint class in models.py. We need to add the followiung code. That will push information about the sprint to Blockchain when new sprint is created. def save(self, *args, **kwargs): # Save to database first is_new = self.pk is None super().save(*args, **kwargs) # Push to Ethereum only for new entries if is_new: try: tx_hash = push_sprint_to_chain(self) print(f"✅ Sprint pushed to Ethereum. TX Hash: {tx_hash}") except Exception as e: print(f"⚠️ Failed to push Sprint to blockchain: {e}")

Changes in the Views.py

We need to modify Sprint class in views.py. We need to explicitly call the save method for Sprint Class from models.py (one we created in the previous step). from contracts.sprint_contract import push_sprint_to_chain def sprint_create(request): if request.method == 'POST': form = SprintForm(request.POST) if form.is_valid(): sprint = form.save() # Save the Sprint instance try: tx_hash = push_sprint_to_chain(sprint) print(f"✅ Sprint pushed to blockchain: {tx_hash}") except Exception as e: print(f"⚠️ Blockchain push failed: {e}") return redirect('sprint_list') else: form = SprintForm() return render(request, 'blockchain_sprint_manager/sprint_form.html', {'form': form})

Testing our code

Let’s start the most exciting phase of our blog. That is, testing out our code.

1. We will start with starting out our server. Please browse to your Django Root folder (one that contains manage.py) and issue following command: python3 manage.py runserver 0.0.0.0:80 Start our Django Server

2. Open up your favorite Web Browser and enter the URL for your Django Application. E.g., in my case it is: http://35.226.208.133 Start our Django Server

3. Now browse to the http://35.226.208.133/project/create/ page to add a new project.

4. Add data and then click on ‘Save’ button Start our Django Server

5. Check the Django command line output to see if project data is successfully written to Blockchain. Django command line

6. Similarly, check the command line output of ganache Blockchain, you will see information about the inserted block Command line output of Ganache Blockchain

7. Similarly, repeat the process for sprint creation. Browse to http://35.226.208.133/sprint/create/, add relevant information and save to create the new sprint. This data will be pushed to the block chain.

Download Complete Code

You are welcome to browse and use the test code. You can find this code at: Ethereum Project Manager on Github

Looking for a reliable tech partner? FAMRO-LLC can help you!

Our development rockstars excel in creating robust and scalable solutions using Django, a powerful Python framework known for its rapid development capabilities and clean, pragmatic design. FAMRO’s team ensures that complex web applications are built quickly and with precision—enabling businesses to focus on growth while we handle the backend complexity.

But that’s not all. FAMRO-LLC also brings deep expertise in blockchain development and smart contract engineering. Whether you’re looking to integrate decentralized logic into your applications or automate business workflows using Ethereum and Solidity, our team can design, deploy, and secure smart contract solutions tailored to your needs.

On the deployment side, our Infrastructure team takes charge with Kubernetes, the leading platform for container orchestration. Their deep knowledge ensures applications are seamlessly deployed, scaled, and managed in cloud environments. By automating service discovery, load balancing, and resource scaling, we guarantee high performance and resilience.

This powerful combination of Django development, smart contract expertise, and cloud-native DevOps positions FAMRO-LLC as your go-to partner for delivering end-to-end, scalable, and future-ready technology solutions.

Please don't hesitate to Contact us for free initial consultation.

Our solutions for your business growth

Our services enable clients to grow their business by providing customized technical solutions that improve infrastructure, streamline software development, and enhance project management.

Our technical consultancy and project management services ensure successful project outcomes by reviewing project requirements, gathering business requirements, designing solutions, and managing project plans with resource augmentation for business analyst and project management roles.

Read More
2
Infrastructure / DevOps
3
Project Management
4
Technical Consulting