Cadastro e Gerenciamento de Imóveis com Bootstrap e SQLite3
Gerenciar seu portfólio de imóveis ficou mais simples! Com essa solução prática, você pode cadastrar imóveis, filtrar por tipo e manter o controle de locações, tudo com um design responsivo. Veja como funciona:
Cadastro de Imóveis
Cadastre imóveis inserindo detalhes como código, tipo (APARTAMENTO, KITNET, CASA), endereço e valor.
Listagem na Homepage
Imóveis são exibidos por tipo, com um filtro eficiente para facilitar a busca.
Cadastro de Clientes
Registre seus clientes com nome, e-mail e telefone, mantendo o histórico completo.
Registro de Locação
Associe clientes aos imóveis e defina o período de locação com as datas de início e término.
Relatório de Imóveis Locados
Tenha acesso a relatórios atualizados sobre os imóveis locados.
Tutorial Completo
O projeto é desenvolvido com Bootstrap para garantir uma experiência responsiva, e utiliza SQLite3 para armazenar os dados de forma robusta.
Modelos Django
Cadastro de Clientes
class Client(models.Model):
name = models.CharField(max_length=100)
email = models.EmailField(max_length=200)
phone = models.CharField(max_length=15)
def __str__(self):
return "{} - {}".format(self.name, self.email)
class Meta:
verbose_name = 'Cliente'
verbose_name_plural = 'Clientes'
ordering = ['-id']
Cadastro de Imóveis
class TypeImmobile(models.TextChoices):
APARTMENT = 'APARTAMENTO','APARTAMENTO'
KITNET = 'KITNET','KITNET'
HOUSE = 'CASA','CASA'
class Immobile(models.Model):
code = models.CharField(max_length=100)
type_item = models.CharField(max_length=100, choices=TypeImmobile.choices)
address = models.TextField()
price = models.DecimalField(max_digits=10,decimal_places=2)
is_locate = models.BooleanField(default=False)
def __str__(self):
return "{} - {}".format(self.code, self.type_item)
class Meta:
verbose_name = 'Imóvel'
verbose_name_plural = 'Imóveis'
ordering = ['-id']
Cadastrar Imagens dos Imóveis
class ImmobileImage(models.Model):
image = models.ImageField('Imagens', upload_to='images')
immobile = models.ForeignKey(Immobile, related_name='immobile_images', on_delete=models.CASCADE)
def __str__(self):
return self.immobile.code
Registrar Locação
class RegisterLocation(models.Model):
immobile = models.ForeignKey(Immobile, on_delete=models.CASCADE, related_name='reg_location')
client = models.ForeignKey(Client, on_delete=models.CASCADE)
dt_start = models.DateTimeField('Início')
dt_end = models.DateTimeField('Fim')
create_at = models.DateField(default=datetime.now, blank=True)
def __str__(self):
return "{} - {}".format(self.client, self.immobile)
class Meta:
verbose_name = 'Registrar Locação'
verbose_name_plural = 'Registrar Locação'
ordering = ['-id']
class ImmobileImageInlineAdmin(admin.TabularInline):
model = models.ImmobileImage
extra = 0
class ImmobileAdmin(admin.ModelAdmin):
inlines = [ImmobileImageInlineAdmin]
admin.site.register(models.Immobile, ImmobileAdmin)
O código está correto e organizado conforme os padrões do Django Admin.
Listagem de Imóveis
Views
def list_location(request):
immobiles = Immobile.objects.filter(is_locate=False)
context = {
'immobiles': immobiles
}
return render(request, 'list-location.html', context)
URLs
from django.urls import path
from myapp import views
urlpatterns = [
path('', views.list_location, name='list-location'),
]
Template
{% extends 'base.html' %}
{% block title %}Lista de Locações{% endblock %}
{% block content %}
<div class="container">
<div class="cards">
{% for immobile in immobiles %}
<div class="card-item h-100">
{% for el in immobile.immobile_images.all %}
{% if forloop.first %}
<img src="{{el.image.url}}" class="card-image" width="100%" height="320" alt="{{el.id}}">
{% endif %}
{% endfor %}
<div class="card-body p-3">
<p>Código: {{immobile.code}}</p>
<p>Endereço: {{immobile.address}}</p>
<p>Valor: {{immobile.price}}</p>
<div class="d-flex justify-content-between align-items-center">
<div class="badge bg-success">Tipo: {{immobile.type_item}}</div>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
{% endblock %}
Estilos com Flexbox
.cards {
display: flex;
flex-wrap: wrap;
align-items: stretch;
}
.card-item {
flex: 0 0 25rem;
box-sizing: border-box;
margin: 1rem 0.25em;
border-radius: 10px;
border: 1px solid;
}
.card-image {
border-radius: 10px;
}
Carrossel de Imagens dos Imóveis
<div class="card-image">
<div id="carouselIndicators{{immobile.id}}" class="carousel slide" data-bs-ride="false">
<div class="carousel-indicators">
{% for el in immobile.immobile_images.all %}
{% if forloop.first %}
<button type="button" data-bs-target="#carouselIndicators{{immobile.id}}" data-bs-slide-to="{{forloop.counter0}}" class="active" aria-current="true" aria-label="Slide {{forloop.counter0}}"></button>
{% else %}
<button type="button" data-bs-target="#carouselIndicators{{immobile.id}}" data-bs-slide-to="{{forloop.counter0}}" aria-label="Slide {{forloop.counter0}}"></button>
{% endif %}
{% endfor %}
</div>
<div class="carousel-inner">
{% for el in immobile.immobile_images.all %}
<div class="carousel-item {% if forloop.first %}active{% endif %}">
<img src="{{el.image.url}}" class="card-image" width="100%" height="320" alt="{{el.id}}">
</div>
{% endfor %}
</div>
<button class="carousel-control-prev" type="button" data-bs-target="#carouselIndicators{{immobile.id}}" data-bs-slide="prev">
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
<span class="visually-hidden">Previous</span>
</button>
<button class="carousel-control-next" type="button" data-bs-target="#carouselIndicators{{immobile.id}}" data-bs-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="visually-hidden">Next</span>
</button>
</div>
</div>
Aqui está o código completo para o seu aplicativo Django com os formulários para Cliente, Imóvel e Locação:
Formulário Cliente
- myapp/forms.py:
class ClientForm(forms.ModelForm):
class Meta:
model = Client
fields = '__all__'
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field_name, field in self.fields.items():
field.widget.attrs['class'] = 'form-control'
- myapp/views.py:
def form_client(request):
form = ClientForm()
if request.method == 'POST':
form = ClientForm(request.POST)
if form.is_valid():
form.save()
return redirect('list-location')
return render(request, 'form-client.html', {'form': form})
- myapp/urls.py:
urlpatterns = [
path('', views.list_location, name='list-location'),
path('form-client/', views.form_client, name='client-create'),
]
- myapp/templates/form-client.html:
{% extends 'base.html' %}
{% block title %}Cadastrar Cliente{% endblock %}
{% load static %}
{% block content %}
<div class="container">
<div class="d-flex gap-4 mt-3">
<img src="{% static 'images/client.jpg' %}" class="card-image" width="100%" height="500" alt="client">
<form class="col-md-4" action="{% url 'client-create' %}" method="post">
{% csrf_token %}
<h3>Cadastrar Cliente</h3>
{% for field in form %}
<div class="mt-3">
{{field.label}}
{{field}}
</div>
{% endfor %}
<input type="submit" class="btn btn-primary mt-3" value="Salvar">
</form>
</div>
</div>
{% endblock %}
Formulário Imóvel
- myapp/forms.py:
class ImmobileForm(forms.ModelForm):
immobile = forms.ImageField(widget=forms.ClearableFileInput(attrs={'multiple': True}))
class Meta:
model = Immobile
fields = '__all__'
exclude = ('is_locate',)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field_name, field in self.fields.items():
if field.widget.__class__ in [forms.CheckboxInput, forms.RadioSelect]:
field.widget.attrs['class'] = 'form-check-input'
else:
field.widget.attrs['class'] = 'form-control'
- myapp/views.py:
def form_immobile(request):
form = ImmobileForm()
if request.method == 'POST':
form = ImmobileForm(request.POST, request.FILES)
if form.is_valid():
immobile = form.save()
files = request.FILES.getlist('immobile')
if files:
for f in files:
ImmobileImage.objects.create(immobile=immobile, image=f)
return redirect('list-location')
return render(request, 'form-immobile.html', {'form': form})
- myapp/urls.py:
urlpatterns = [
path('', views.list_location, name='list-location'),
path('form-client/', views.form_client, name='client-create'),
path('form-immobile/', views.form_immobile, name='immobile-create'),
]
- myapp/templates/form-immobile.html:
{% extends 'base.html' %}
{% block title %}Cadastrar Imóvel{% endblock %}
{% load static %}
{% block content %}
<div class="container">
<div class="d-flex gap-4 mt-3">
<img src="{% static 'images/imovel.png' %}" class="card-image" width="100%" height="500" alt="client">
<form class="col-md-6" action="{% url 'immobile-create' %}" method="post" enctype="multipart/form-data">
{% csrf_token %}
<h3>Cadastrar Imóvel</h3>
{% for field in form %}
<div class="mt-3">
{{field.label}}
{{field}}
</div>
{% endfor %}
<input type="submit" class="btn btn-primary mt-3" value="Salvar">
</form>
</div>
</div>
{% endblock %}
Formulário Registro de Locação
- myapp/forms.py:
class RegisterLocationForm(forms.ModelForm):
dt_start = forms.DateTimeField(widget=forms.DateInput(format='%d-%m-%Y', attrs={'type': 'date'}))
dt_end = forms.DateTimeField(widget=forms.DateInput(format='%d-%m-%Y', attrs={'type': 'date'}))
class Meta:
model = RegisterLocation
fields = '__all__'
exclude = ('immobile', 'create_at',)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field_name, field in self.fields.items():
field.widget.attrs['class'] = 'form-control'
- myapp/views.py:
def form_location(request, id):
get_locate = Immobile.objects.get(id=id)
form = RegisterLocationForm()
if request.method == 'POST':
form = RegisterLocationForm(request.POST)
if form.is_valid():
location_form = form.save(commit=False)
location_form.immobile = get_locate
location_form.save()
get_locate.is_locate = True
get_locate.save()
return redirect('list-location')
context = {'form': form, 'location': get_locate}
return render(request, 'form-location.html', context)
- myapp/urls.py:
urlpatterns = [
path('', views.list_location, name='list-location'),
path('form-client/', views.form_client, name='client-create'),
path('form-immobile/', views.form_immobile, name='immobile-create'),
path('form-location/<int:id>/', views.form_location, name='location-create'),
]
- myapp/templates/form-location.html:
{% extends 'base.html' %}
{% block title %}Cadastrar Locação{% endblock %}
{% block content %}
<div class="container">
<div class="d-flex gap-4 mt-4">
<form class="col-md-4" action="{% url 'location-create' location.id %}" method="post">
{% csrf_token %}
<h3>Formulário de Registro Locação</h3>
{% for field in form %}
<div class="mt-3">
{{field.label}}
{{field}}
</div>
{% endfor %}
<input type="submit" class="btn btn-primary mt-3" value="Locar">
</form>
</div>
</div>
{% endblock %}
Como temos um context location
para objeto podemos colocar algumas informações no templates.
<!-- Informções do Objeto Here -->
<div class="">
<div id="carouselExampleControls" class="carousel slide" data-bs-ride="false">
<div class="carousel-inner">
{% for el in location.immobile_images.all %}
<div class="carousel-item {% if forloop.first %}active{% endif %}">
<img src="{{el.image.url}}" class="card-image" width="100%" height="500" alt="{{el.id}}">
</div>
{% endfor %}
</div>
<button class="carousel-control-prev" type="button" data-bs-target="#carouselExampleControls" data-bs-slide="prev">
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
<span class="visually-hidden">Previous</span>
</button>
<button class="carousel-control-next" type="button" data-bs-target="#carouselExampleControls" data-bs-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="visually-hidden">Next</span>
</button>
</div>
<div class="mt-3">
<p>Codígo: {{location.code}}</p>
<p>Endereço: {{location.address}}</p>
<p>Valor: {{location.price}}</p>
<div class="badge bg-success">Tipo: {{location.type_item}}</div>
</div>
</div>
Navbar
- navbar.html:
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container-fluid">
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarTogglerDemo03" aria-controls="navbarTogglerDemo03" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<a class="navbar-brand" href="#">Myapp</a>
<div class="collapse navbar-collapse" id="navbarTogglerDemo03">
<ul class="navbar-nav gap-3 mb-2 mb-lg-0 mx-auto">
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="/">Inicio</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/">Relatório</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
Cadastrar
</a>
<ul class="dropdown-menu dropdown-menu-dark">
<li>
<a class="dropdown-item" href="{% url 'client-create' %}">Cliente</a>
</li>
<li>
<hr class="dropdown-divider">
</li>
<li>
<a class="dropdown-item" href="{% url 'immobile-create' %}">Imóvel</a>
</li>
</ul>
</li>
</ul>
<a class="navbar-brand" href="#">@eticialima</a>
</div>
</div>
</nav>
Exibindo Mensagens no Django
message.html
{% if messages %}
<div class="messages">
{% for message in messages %}
<div {% if message.tags %} class="alert {{ message.tags }}"{% endif %} role="alert">{{ message }}</div>
{% endfor %}
</div>
{% endif %}
core/settings.py
# --- Messages --- #
from django.contrib.messages import constants
MESSAGE_TAGS = {
constants.ERROR: 'alert-danger',
constants.WARNING: 'alert-warning',
constants.DEBUG: 'alert-danger',
constants.SUCCESS: 'alert-success',
constants.INFO: 'alert-info',
}
Relatório Simples
myapp/views.py
def reports(request): # Relatórios
immobile = Immobile.objects.all()
return render(request, 'reports.html', {'immobiles': immobile})
myapp/urls.py
from django.urls import path
from myapp import views
urlpatterns = [
...
path('reports/', views.reports, name='reports'),
...
]
myapp/templates/reports.html
Para acessar dados de uma tabela relacionada, estamos utilizando for immobile.reg_location.all
para buscar os registros de Locação relacionados à tabela de Imóveis.
<td scope="row">{% for location in immobile.reg_location.all %}{{ location.dt_start|date:"d/m/Y" }}{% endfor %}</td>
<td scope="row">{% for location in immobile.reg_location.all %}{{ location.dt_end|date:"d/m/Y" }}{% endfor %}</td>
<td scope="row">{% for location in immobile.reg_location.all %}{{ location.client }}{% endfor %}</td>
Template Base
{% extends 'base.html' %}
{% block title %}Relatório{% endblock %}
{% block content %}
<div class="container">
<!-- Tabela com informações de Registro de Locação -->
<table class="table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Registro Inicial</th>
<th scope="col">Registro Final</th>
<th scope="col">Cliente</th>
<th scope="col">Código</th>
<th scope="col">Imóvel</th>
<th scope="col">Valor</th>
<th scope="col">Locado</th>
</tr>
</thead>
<tbody>
{% for immobile in immobiles %}
<tr>
<td scope="row">{{ immobile.id }}</td>
<td scope="row">{% for location in immobile.reg_location.all %}{{ location.dt_start|date:"d/m/Y" }}{% endfor %}</td>
<td scope="row">{% for location in immobile.reg_location.all %}{{ location.dt_end|date:"d/m/Y" }}{% endfor %}</td>
<td scope="row">{% for location in immobile.reg_location.all %}{{ location.client }}{% endfor %}</td>
<td scope="row">{{ immobile.code }}</td>
<td scope="row">{{ immobile.type_item }}</td>
<td scope="row">R$ {{ immobile.price }}</td>
<td scope="row">
{% if immobile.is_locate %}
<i class="fas fa-check-circle fa-2x link-success"></i>
{% else %}
<i class="fas fa-minus-circle fa-2x link-danger"></i>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}
Tag do Font Awesome para ícones
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.15.4/css/all.css" integrity="sha384-DyZ88mC6Up2uqS4h/KRgHuoeGwBcD4Ng9SiP4dIRy0EXTlnuz47vAwmeGwVChigm" crossorigin="anonymous"/>
Filtro pelo Status do Imóvel (Locado ou Não)
views.py
def reports(request): # Relatórios
immobile = Immobile.objects.all()
is_locate = request.GET.get('is_locate')
if is_locate:
immobile = Immobile.objects.filter(is_locate=is_locate)
return render(request, 'reports.html', {'immobiles': immobile})
HTML para filtro de status de locação
<div class="row g-3 align-items-center m-3 bg-light p-3">
<div class="col-auto">
<form action="{% url 'reports' %}">
<label>Status Locação</label>
<select name="is_locate" class="form-select" onchange="this.form.submit()">
<option></option>
<option value="True" {% if request.GET.is_locate == 'True' %}selected{% endif %}>LOCADO</option>
<option value="False" {% if request.GET.is_locate == 'False' %}selected{% endif %}>NÃO LOCADO</option>
</select>
</form>
</div>
<div class="col-auto">
<a class="btn btn-danger" href="{% url 'reports' %}"><i class="fas fa-window-close"></i></a>
</div>
</div>
Filtro pelo Tipo de Imóvel
views.py
def reports(request): # Relatórios
immobile = Immobile.objects.all()
type_item = request.GET.get('type_item')
is_locate = request.GET.get('is_locate')
if type_item:
immobile = Immobile.objects.filter(type_item=type_item)
if is_locate:
immobile = Immobile.objects.filter(is_locate=is_locate)
return render(request, 'reports.html', {'immobiles': immobile})
HTML para filtro de tipo de imóvel
<div class="col-auto">
<form action="{% url 'reports' %}">
<label>Tipo de Imóvel</label>
<select name="type_item" class="form-select" onchange="this.form.submit()">
<option></option>
<option value="APARTAMENTO" {% if request.GET.type_item == 'APARTAMENTO' %}selected{% endif %}>APARTAMENTO</option>
<option value="KITNET" {% if request.GET.type_item == 'KITNET' %}selected{% endif %}>KITNET</option>
<option value="CASA" {% if request.GET.type_item == 'CASA' %}selected{% endif %}>CASA</option>
</select>
</form>
</div>
Pesquisar pelo Nome ou E-mail do Cliente
Utilizando a biblioteca Q
para filtros mais complexos:
views.py
from django.db.models import Q
def reports(request): # Relatórios
immobile = Immobile.objects.all()
client = request.GET.get('client')
type_item = request.GET.get('type_item')
is_locate = request.GET.get('is_locate')
if client:
immobile = Immobile.objects.filter(
Q(reg_location__client__name__icontains=client) |
Q(reg_location__client__email__icontains=client))
if type_item:
immobile = Immobile.objects.filter(type_item=type_item)
if is_locate:
immobile = Immobile.objects.filter(is_locate=is_locate)
return render(request, 'reports.html', {'immobiles': immobile})
HTML para pesquisa por cliente
<div class="col-auto">
<label>Nome do Cliente ou E-mail</label>
<form class="d-flex" action="{% url 'reports' %}">
<input name="client" type="search" class="form-control me-2" placeholder="Pesquisar por cliente..." aria-label="Search">
<button class="btn btn-success" type="submit"><i class="fas fa-search"></i></button>
</form>
</div>
Filtro por Intervalo de Data
views.py
def reports(request): # Relatórios
immobile = Immobile.objects.all()
client = request.GET.get('client')
dt_start = request.GET.get('dt_start')
dt_end = request.GET.get('dt_end')
type_item = request.GET.get('type_item')
is_locate = request.GET.get('is_locate')
if client:
immobile = Immobile.objects.filter(Q(reg_location__client__name__icontains=client) | Q(reg_location__client__email__icontains=client))
if dt_start and dt_end:
immobile = Immobile.objects.filter(reg_location__create_at__range=[dt_start, dt_end])
if type_item:
immobile = Immobile.objects.filter(type_item=type_item)
if is_locate:
immobile = Immobile.objects.filter(is_locate=is_locate)
return render(request, 'reports.html', {'immobiles': immobile})
HTML para filtro de data
<div class="col-auto">
<form action="{% url 'reports' %}">
<label>Período</label>
<div class="d-flex">
<input type="date" name="dt_start" class="form-control" placeholder="Início" />
<input type="date" name="dt_end" class="form-control ms-2" placeholder="Final" />
<button class="btn btn-info ms-2" type="submit"><i class="fas fa-search"></i></button>
</div>
</form>
</div>
Esse código cria um filtro com dois campos de data (dt_start
e dt_end
), permitindo ao usuário escolher um intervalo de datas. O botão de pesquisa envia os dados para a URL associada, aplicando o filtro para a consulta dos registros.