Django适配Heroku:将用户头像存Firebase,URL存入SQLite
Hey there! Let's get that avatar upload working on Heroku by switching to Firebase Storage—since Heroku's ephemeral filesystem can't handle dynamic file storage long-term. Here's a step-by-step solution tailored to your existing Django code:
1. Install Required Packages
First, add these packages to your requirements.txt and install them locally:
firebase-admin>=6.2.0 python-dotenv>=1.0.0
Run pip install -r requirements.txt to get them set up.
2. Set Up Firebase Storage
- Create a Firebase project (if you don't have one already) and enable Cloud Storage in the Firebase console.
- Go to Project Settings > Service Accounts and download the JSON key file for your service account.
- For security, store this key's content as an environment variable (instead of committing the file to version control). Use a
.envfile locally, and on Heroku, set it via the CLI.
3. Configure Firebase in Django
Add this configuration to your settings.py (or create a separate firebase_config.py and import it):
import firebase_admin from firebase_admin import credentials, storage import os import json # Load Firebase credentials from environment variable service_account = json.loads(os.environ.get("FIREBASE_SERVICE_ACCOUNT")) cred = credentials.Certificate(service_account) # Initialize Firebase app with storage bucket firebase_admin.initialize_app(cred, { "storageBucket": "your-firebase-bucket-name.appspot.com" # Replace with your actual bucket URL })
- Replace
your-firebase-bucket-name.appspot.comwith the URL found in your Firebase Storage console.
4. Update Your Profile Model
We'll store a Firebase URL instead of a local file, so modify your models.py:
from django.db import models from django.contrib.auth.models import User from datetime import date class Profile(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE) birth_date = models.DateField('Fecha de nacimiento', default=date.today) # Replace ImageField with URLField photo = models.URLField( 'Foto de perfil', default='https://storage.googleapis.com/your-bucket-name/default.png' # Replace with your default avatar's Firebase URL )
- Upload your
default.pngto Firebase Storage first, then paste its public URL as the default value.
5. Adjust the Profile Form
Keep the ImageField to let users upload files, but we'll handle the Firebase upload in the view:
# forms.py from django import forms from django.contrib.auth.models import User from django.contrib.auth.forms import UserChangeForm from .models import Profile from django.forms.widgets import DatePickerInput class UEditF(UserChangeForm): class Meta: model = User fields = ['first_name', 'last_name'] class ProfileForm(forms.ModelForm): birth_date = forms.DateField(widget=DatePickerInput(format='%d/%m/%Y'), label='Fecha de nacimiento') # Keep ImageField for file upload, make it optional to skip avatar updates photo = forms.ImageField(label="Foto de perfil", required=False) class Meta: model = Profile fields = ['birth_date', 'photo']
6. Modify the Edit Profile View
Update your views.py to handle uploading avatars to Firebase and saving the URL to the database:
from django.shortcuts import render, redirect from .forms import UEditF, ProfileForm from firebase_admin import storage import time def upload_to_firebase(file, user_id): """Helper function to upload file to Firebase and return its public URL""" bucket = storage.bucket() # Generate a unique filename to avoid overwriting existing avatars filename = f"avatars/user_{user_id}_{int(time.time())}_{file.name}" blob = bucket.blob(filename) # Upload file with correct content type blob.upload_from_file(file, content_type=file.content_type) # Make the file publicly accessible blob.make_public() return blob.public_url def edit_profile(request): if request.method == 'POST': form = UEditF(request.POST, instance=request.user) extended_profile_form = ProfileForm(request.POST, request.FILES, instance=request.user.profile) if form.is_valid() and extended_profile_form.is_valid(): form.save() # Handle avatar upload logic profile = extended_profile_form.save(commit=False) if request.FILES.get('photo'): uploaded_file = request.FILES['photo'] profile.photo = upload_to_firebase(uploaded_file, request.user.id) profile.save() return redirect('/polls/perfil') else: form = UEditF(instance=request.user) extended_profile_form = ProfileForm(instance=request.user.profile) context = { 'form': form, 'extended_profile_form':extended_profile_form } form.fields['password'].help_text = 'Para cambiar la contraseña has clic en el menú superior derecho "Cambiar contraseña"' return render(request, 'registration/edit_profile.html', context)
7. Update Your Template
Make sure your template uses the stored URL directly instead of a local file path. For example, in your profile display template:
<!-- Replace {{ user.profile.photo.url }} with {{ user.profile.photo }} --> <img src="{{ user.profile.photo }}" alt="Foto de perfil" class="avatar">
8. Deploy to Heroku
- Push your code changes to Heroku.
- Set the required environment variables via Heroku CLI:
# Replace the JSON string with your service account key content heroku config:set FIREBASE_SERVICE_ACCOUNT='{"type": "service_account", "project_id": "...", ...}' heroku config:set DJANGO_SETTINGS_MODULE=your_project.settings - Run migrations on Heroku:
heroku run python manage.py migrate
Quick Notes
- Ensure your Firebase Storage bucket has public read access (adjust this in the Firebase Storage rules if needed).
- Add error handling in the
upload_to_firebasefunction (e.g.,try-exceptblocks) to catch upload failures and inform the user.
内容的提问来源于stack exchange,提问作者Katherine Zambrano

