First of all, I try to make an Rest-API with Flask MongoEngine. I create a model which is;
class Project(db.Document):
name = db.StringField(validation=validate_project_name)
created_at = db.DateTimeField()
updated_at = db.DateTimeField()
def save(self, *args, **kwargs):
if not self.created_at:
self.created_at = datetime.datetime.now()
self.updated_at = datetime.datetime.now()
return super(Project, self).save(*args, **kwargs)
def __str__(self):
return self.name
and I also created a Form ;
ProjectForm = model_form(Project, exclude=["created_at", "updated_at"])
and also the custom validation is ;
def validate_project_name(name, min=5, max=25):
if len(name) <= min or len(name) >= max:
raise ValidationError(
f"Proje adı en az {min} en fazla {max} karakter olabilir!"
)
return True
I want to validate with ProjectFrom when i send the data which I get from request body that's why i prepare that code ;
#project.route("/create", methods=["POST"])
def create_project():
data = MultiDict(request.json)
form = ProjectForm(data, meta={"csrf": False})
if form.validate():
if Project(name=form.data["name"]).save():
## will take create action
return True
else:
return Response({form.errors}, status=400)
It returns true when i request but when i change de data on request.body it still returns true where do i mistake can someone help me and explain the logic. Or Should i create a middleware for the validation also i tried that. and i created something. But i don't know is it the best way.
Thanks in advance...
Middleware that i tried to validate data;
def creation_project_validation(func):
#wraps(func)
def decorated_function(*args, **kwargs):
if request.method == "POST":
data = request.json
if data.get("name") is None:
return Response({"Name is required field!"}, status=400)
if validate_project_name(data.get("name")):
return Response(
{"Karakter uzunluğu istenilen şekilde değil!"}, status=400
)
return func(*args, **kwargs)
else:
Response("Method not allowed", status=405)
return decorated_function
Related
Hi everyone I integrate my Django Web app with mail chimp . in my admin panel when I open marketing preference it give me error . and it do not subscribe my users when I click on subscribe my user remain unsubscribe when i hit save .the error I got is
{'type': 'https://mailchimp.com/developer/marketing/docs/errors/', 'title': 'Invalid Resource', 'status': 400, 'detail': "The resource submitted could not be validated. For field-specific details, see the 'errors' array.", 'instance': '2b647b4f-6e58-439f-8c91-31a3223600a9', 'errors': [{'field': 'email_address', 'message': 'This value should not be blank.'}]}
my models.py file is:
class MarketingPreference(models.Model):
user =models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
subscribed =models.BooleanField(default=True)
mailchimp_subscribed = models.NullBooleanField(blank=True)
mailchimp_msg =models.TextField(null=True , blank=True)
timestamp =models.DateTimeField(auto_now_add=True)
updated =models.DateTimeField(auto_now=True)
def __str__(self):
return self.user.email
def marketing_pref_create_reciever(sender, instance, created, *args, **kwargs):
if created:
status_code, response_data = Mailchimp().subscribe(instance.user.email)
print(status_code, response_data)
post_save.connect(marketing_pref_create_reciever, sender=MarketingPreference)
def marketing_pref_update_reciever(sender, instance, *args, **kwargs):
if instance.subscribed != instance.mailchimp_subscribed:
if instance.subscribed:
#subscribing user
status_code, response_data = Mailchimp().subscribe(instance.user.email)
else:
#unsubscribing user
status_code, response_data = Mailchimp().unsubscribe(instance.user.email)
if response_data['status'] =='subscribed':
instance.subscribed = True
instance.mailchimp_subscribed = True
instance.mailchimp_msg = response_data
else:
instance.subscribed = False
instance.mailchimp_subscribed = False
instance.mailchimp_msg = response_data
pre_save.connect(marketing_pref_update_reciever, sender=MarketingPreference)
def make_marketing_pref_reciever(sender, instance, created, *args, **kwargs):
if created:
MarketingPreference.objects.get_or_create(user=instance)
post_save.connect(make_marketing_pref_reciever , sender=settings.AUTH_USER_MODEL)
my utils.py is:
MAILCHIMP_API_KEY = getattr(settings, "MAILCHIMP_API_KEY" , None)
MAILCHIMP_DATA_CENTER = getattr(settings, "MAILCHIMP_DATA_CENTER" , None)
MAILCHIMP_EMAIL_LIST_ID = getattr(settings, "MAILCHIMP_EMAIL_LIST_ID" , None)
def check_email(email):
if not re.match(r".+#.+\..+",email):
raise ValueError("String passed is not a valid email address")
return email
def get_subscriber_hash(member_email):
#check email
check_email(member_email)
member_email = member_email.lower().encode()
m = hashlib.md5(member_email)
return m.hexdigest()
class Mailchimp(object):
def __init__(self):
super(Mailchimp, self).__init__()
self.key = MAILCHIMP_API_KEY
self.api_url = "https://{dc}.api.mailchimp.com/3.0/".format(dc=MAILCHIMP_DATA_CENTER)
self.list_id = MAILCHIMP_EMAIL_LIST_ID
self.list_endpoint = '{api_url}/lists/{list_id}'.format(api_url=self.api_url, list_id=self.list_id)
def get_members_endpoint(self):
return self.list_endpoint + "/members"
def change_subscription_status(self, email, status='unsubscribed'):
hashed_email = get_subscriber_hash(email)
endpoint = self.get_members_endpoint() +"/" + hashed_email
data = {
"status":self.check_valid_status(status)
}
r = requests.put(endpoint, auth=("",self.key), data=json.dumps(data))
return r.status_code, r.json()
def check_subscription_status(self,email):
hashed_email = get_subscriber_hash(email)
endpoint = self.get_members_endpoint() +"/" + hashed_email
r = requests.get(endpoint, auth=("", self.key))
return r.status_code, r.json()
def check_valid_status(self, status):
choices = ['subscribed' , 'unsubscribed', 'cleaned' , 'pending']
if status not in choices:
raise ValueError("not a valid choice for email status")
return status
def add_email(self,email):
status = "subscribed"
self.check_valid_status(status)
data = {
"email_address":email,
"status": status
}
endpoint = self.get_members_endpoint()
r = requests.post(endpoint, auth=("",self.key), data=json.dumps(data))
return self.change_subscription_status(email, status='subscribed')
def unsubscribe(self, email):
return self.change_subscription_status(email, status='unsubscribed')
def subscribe(self, email):
return self.change_subscription_status(email, status='subscribed')
def pending(self, email):
return self.change_subscription_status(email, status='pending')
mixins.py is:
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
class CsrfExemptMixin(object):
#method_decorator(csrf_exempt)
def dispatch(self, request,*args, **kwargs):
return super(CsrfExemptMixin, self).dispatch(request,*args, **kwargs)
and my views.py is:
from .mixins import CsrfExemptMixin
from .models import MarketingPreference
from . utils import Mailchimp
MAILCHIMP_EMAIL_LIST_ID = getattr(settings, "MAILCHIMP_EMAIL_LIST_ID" , None)
# Create your views here.
class MarketingPreferenceUpdateView(SuccessMessageMixin, UpdateView):
form_class = MarketingPreferenceForm
template_name = 'base/forms.html'
success_url = '/settings/email/' #this is a builtin method and by default it will go to marketig preference
success_message = 'Your email preferences have been updated. Thank you'
def dispatch(self, *args, **kwargs): #when user came with incognito email will not show to him it will redirect him back to login page
user = self.request.user
if not user.is_authenticated:
return redirect("/login/?next=/settings/email/") #HttpResponse("not allowed", status=400)
return super(MarketingPreferenceUpdateView, self).dispatch(*args,**kwargs)#(request, *args...)
def get_context_data(self, *args, **kwargs):
context = super(MarketingPreferenceUpdateView, self).get_context_data(*args,**kwargs)
context['title'] = 'Update Email Preference'
return context
def get_object(self):
user = self.request.user
obj , created = MarketingPreference.objects.get_or_create(user=user)
return obj
class MailchimpWebhookView(CsrfExemptMixin,View): #it will not work because our web application is not deployes yet and webhook mailchimp do not work with local host
#def get(self, *args, **kwargs):
# return HttpResponse('thank you', status=200)
def post(self, request, *args, **kwargs):
data = request.POST
list_id = data.get('data[list_id]')
if str(list_id) == str(MAILCHIMP_EMAIL_LIST_ID): # I CHECK THAT DATA DATA IS THE RIGHT LIST
hook_type = data.get("type")
email = data.get('data[email]')
response_status, response = Mailchimp().check_subscription_status(email)
sub_status = response['status']
is_subbed = None
mailchimp_subbed = None
if sub_status == "subscribed":
is_subbed, mailchimp_subbed = (True, True)
elif sub_status == "unsubscribed":
is_subbed, mailchimp_subbed = (False, False)
if is_subbed is not None and mailchimp_subbed is not None:
qs = MarketingPreference.objects.filter(user__email__iexact=email)
if qs.exists():
qs.update(subscribed=is_subbed, mailchimp_subscribed=mailchimp_subbed, mailchimp_msg=str(data))
return HttpResponse('thank you', status=200)
Here you have already assigned request.POST to data which is now a dictonary, so to get a value from dictonary you should use the field name of the form widget, as data == request.POST now.
Problem is you are getting wrong key.So your email will always be empty
list_id = data.get('data[list_id]')
email = data.get('data[email]')#You are getting wrong key
It should be like this
list_id = data.get('list_id')
email = data.get('email')
I am absolutely beginners in python, django framework. I don't know how to make custom decorators in django.
I am trying to, if user's profile is 100% completed they can apply job, else they can shows the warning message "your profile is nit completed, please first of all complete your profile then apply this job"
I have following this code
models.py
class profile(models.Model):
user = models.OneToOneField(User, on_delete = models.CASCADE, primary_key=True)
random = models.CharField(max_length=50)
image = models.ImageField(upload_to="profile_pics")
def __str__(self):
return f'{self.user.username} profile'
def get_absolute_url(self):
return reverse('jobseeker:profile_count')
#property
def percentage_complete(self):
percent = { 'random': 50, 'image': 50}
total = 0
if self.random:
total += percent.get('random', 0)
if self.image:
total += percent.get('image', 0)
return total
decorators.py
def complete_profile_required(view_func):
def wrapper_func(request, *args, **Kwargs):
if request.user.profile.percentage_complete == 100:
print(request.user.profile.percentage_complete)
return redirect('jobseeker:apply_job')
else:
return view_func(request, *args, **Kwargs)
return wrapper_func
views.py
#login_required
#jobseeker_required
#complete_profile_required
def apply_job(request):
return render(request, 'apply_job.html')
urls.py
path('apply_job', views.apply_job, name='apply_job')
it will returns the this error
You have to redirect to another view. You are getting the TOO_MANY_REDIRECTS error. Because you are redirecting it the same view.
You have to change your decorator
def complete_profile_required(view_func):
def wrapper_func(request, *args, **Kwargs):
if request.user.profile.percentage_complete == 100:
# change your logic here
return redirect('jobseeker:another_view')
else:
return view_func(request, *args, **Kwargs)
return wrapper_func
I am editing a questionnaire app using a diplay_by_question template where each question is displayed on a single page using a different URL in the form of survey/22; suvey/22-1; survey/22-2 ....
When saving the answers the form should take the actual step we are in and save it in the database.
For example if I am at URL survey/22-2 is referencing to survey ID: 22 and step : 1
In my code it shoud extract from my form the data using the current step:
form = ResponseForm(request.POST, survey=survey, user=request.user, step=kwargs.get('step', 0))
However I do not understand why if the URL is step 1 the step does not update and stay at step 0
Could you please help ?
viexs.py:
class SurveyDetail(View):
def get(self, request, *args, **kwargs):
survey = get_object_or_404(Survey, is_published=True, id=kwargs['id'])
if survey.template is not None and len(survey.template) > 4:
template_name = survey.template
else:
if survey.display_by_question:
template_name = 'survey/survey.html'
else:
template_name = 'survey/one_page_survey.html'
if survey.need_logged_user and not request.user.is_authenticated():
return redirect('%s?next=%s' % (settings.LOGIN_URL, request.path))
categories = Category.objects.filter(survey=survey).order_by('order')
form = ResponseForm(survey=survey, user=request.user,
step=kwargs.get('step', 0))
context = {
'response_form': form,
'survey': survey,
'categories': categories,
}
return render(request, template_name, context)
def post(self, request, *args, **kwargs):
import pdb; pdb.set_trace()
survey = get_object_or_404(Survey, is_published=True, id=kwargs['id'])
if survey.need_logged_user and not request.user.is_authenticated():
return redirect('%s?next=%s' % (settings.LOGIN_URL, request.path))
form = ResponseForm(request.POST, survey=survey, user=request.user, step=kwargs.get('step', 0))
context = {'response_form': form, 'survey': survey,}
if form.is_valid():
session_key = 'survey_%s' % (kwargs['id'],)
if session_key not in request.session:
request.session[session_key] = {}
for key, value in form.cleaned_data.items():
request.session[session_key][key] = value
request.session.modified = True
next_url = form.next_step_url()
response = None
if survey.display_by_question:
save_form = form
if save_form.is_valid():
response = save_form.save()
else:
response = form.save()
if next_url is not None:
return redirect(next_url)
else:
del request.session[session_key]
if response is None:
return redirect('/')
else:
next_ = request.session.get('next', None)
if next_ is not None:
if 'next' in request.session:
del request.session['next']
return redirect(next_)
else:
return redirect('survey-confirmation',
uuid=response.interview_uuid)
if survey.template is not None and len(survey.template) > 4:
template_name = survey.template
else:
if survey.display_by_question:
template_name = 'survey/survey.html'
else:
template_name = 'survey/one_page_survey.html'
return render(request, template_name, context)
form.py:
class ResponseForm(models.ModelForm):
WIDGETS = {
Question.TEXT: forms.Textarea,
Question.SHORT_TEXT: forms.TextInput,
Question.RADIO: forms.RadioSelect,
Question.SELECT: forms.Select,
Question.SELECT_IMAGE: ImageSelectWidget,
Question.SELECT_MULTIPLE: forms.CheckboxSelectMultiple,
}
class Meta(object):
model = Response
fields = ()
def __init__(self, *args, **kwargs):
import pdb; pdb.set_trace()
""" Expects a survey object to be passed in initially """
self.survey = kwargs.pop('survey')
self.user = kwargs.pop('user')
try:
self.step = int(kwargs.pop('step'))
except KeyError:
self.step = None
super(ResponseForm, self).__init__(*args, **kwargs)
self.uuid = uuid.uuid4().hex
self.steps_count = len(self.survey.questions.all())
# add a field for each survey question, corresponding to the question
# type as appropriate.
data = kwargs.get('data')
for i, question in enumerate(self.survey.questions.all()):
is_current_step = i != self.step and self.step is not None
if self.survey.display_by_question and is_current_step:
continue
else:
self.add_question(question, data)
def _get_preexisting_response(self):
""" Recover a pre-existing response in database.
The user must be logged.
:rtype: Response or None"""
if not self.user.is_authenticated():
return None
try:
return Response.objects.get(user=self.user, survey=self.survey)
except Response.DoesNotExist:
LOGGER.debug("No saved response for '%s' for user %s",
self.survey, self.user)
return None
def _get_preexisting_answer(self, question):
""" Recover a pre-existing answer in database.
The user must be logged. A Response containing the Answer must exists.
:param Question question: The question we want to recover in the
response.
:rtype: Answer or None"""
response = self._get_preexisting_response()
if response is None:
return None
try:
return Answer.objects.get(question=question, response=response)
except Answer.DoesNotExist:
return None
def get_question_initial(self, question, data):
""" Get the initial value that we should use in the Form
:param Question question: The question
:param dict data: Value from a POST request.
:rtype: String or None """
initial = None
answer = self._get_preexisting_answer(question)
if answer:
# Initialize the field with values from the database if any
if question.type == Question.SELECT_MULTIPLE:
initial = []
if answer.body == "[]":
pass
elif "[" in answer.body and "]" in answer.body:
initial = []
unformated_choices = answer.body[1:-1].strip()
for unformated_choice in unformated_choices.split(","):
choice = unformated_choice.split("'")[1]
initial.append(slugify(choice))
else:
# Only one element
initial.append(slugify(answer.body))
else:
initial = answer.body
if data:
# Initialize the field field from a POST request, if any.
# Replace values from the database
initial = data.get('question_%d' % question.pk)
return initial
def get_question_widget(self, question):
""" Return the widget we should use for a question.
:param Question question: The question
:rtype: django.forms.widget or None """
try:
return self.WIDGETS[question.type]
except KeyError:
return None
def get_question_choices(self, question):
""" Return the choices we should use for a question.
:param Question question: The question
:rtype: List of String or None """
qchoices = None
if question.type not in [Question.TEXT, Question.SHORT_TEXT,
Question.INTEGER]:
qchoices = question.get_choices()
# add an empty option at the top so that the user has to explicitly
# select one of the options
if question.type in [Question.SELECT, Question.SELECT_IMAGE]:
qchoices = tuple([('', '-------------')]) + qchoices
return qchoices
def get_question_field(self, question, **kwargs):
""" Return the field we should use in our form.
:param Question question: The question
:param **kwargs: A dict of parameter properly initialized in
add_question.
:rtype: django.forms.fields """
FIELDS = {
Question.TEXT: forms.CharField,
Question.SHORT_TEXT: forms.CharField,
Question.SELECT_MULTIPLE: forms.MultipleChoiceField,
Question.INTEGER: forms.IntegerField
}
# logging.debug("Args passed to field %s", kwargs)
try:
return FIELDS[question.type](**kwargs)
except KeyError:
return forms.ChoiceField(**kwargs)
def add_question(self, question, data):
""" Add a question to the form.
:param Question question: The question to add.
:param dict data: The pre-existing values from a post request. """
kwargs = {"label": question.text,
"required": question.required, }
initial = self.get_question_initial(question, data)
if initial:
kwargs["initial"] = initial
choices = self.get_question_choices(question)
if choices:
kwargs["choices"] = choices
widget = self.get_question_widget(question)
if widget:
kwargs["widget"] = widget
field = self.get_question_field(question, **kwargs)
if question.category:
field.widget.attrs["category"] = question.category.name
else:
field.widget.attrs["category"] = ""
# logging.debug("Field for %s : %s", question, field.__dict__)
self.fields['question_%d' % question.pk] = field
def has_next_step(self):
if self.survey.display_by_question:
if self.step < self.steps_count - 1:
return True
return False
def next_step_url(self):
if self.has_next_step():
context = {'id': self.survey.id, 'step': self.step + 1}
return reverse('survey-detail-step', kwargs=context)
else:
return None
def current_step_url(self):
return reverse('survey-detail-step', kwargs={'id': self.survey.id, 'step': self.step})
def save(self, commit=True):
import pdb; pdb.set_trace()
""" Save the response object """
# Recover an existing response from the database if any
# There is only one response by logged user.
response = self._get_preexisting_response()
if response is None:
response = super(ResponseForm, self).save(commit=False)
response.survey = self.survey
response.interview_uuid = self.uuid
if self.user.is_authenticated():
response.user = self.user
response.save()
# response "raw" data as dict (for signal)
data = {
'survey_id': response.survey.id,
'interview_uuid': response.interview_uuid,
'responses': []
}
# create an answer object for each question and associate it with this
# response.
for field_name, field_value in self.cleaned_data.items():
if field_name.startswith("question_"):
# warning: this way of extracting the id is very fragile and
# entirely dependent on the way the question_id is encoded in
# the field name in the __init__ method of this form class.
q_id = int(field_name.split("_")[1])
question = Question.objects.get(pk=q_id)
answer = self._get_preexisting_answer(question)
if answer is None:
answer = Answer(question=question)
answer.body = field_value
data['responses'].append((answer.question.id, answer.body))
LOGGER.debug(
"Creating answer for question %d of type %s : %s", q_id,
answer.question.type, field_value
)
answer.response = response
answer.save()
survey_completed.send(sender=Response, instance=response, data=data)
return response
I have two models:
class Building(models.Model):
label = models.CharField(_("Building label"), blank=False, max_length=255)
def get_absolute_url(self):
return reverse('buildings:config', kwargs={'pk': self.id})
class Floor(models.Model):
label = models.CharField(_("Floor label"), blank=True, max_length=255)
building = models.ForeignKey(Building, null=False)
I have written a custom ModelForm in order to allow the user to change (UpdateView) the building's infos and add a new floor (FloorsConfigForm) in the same page.
The FloorsConfigForm is :
class FloorsConfigForm(forms.ModelForm):
class Meta:
model = Floor
fields = ["label", "building",]
And the view is:
class BuildingConfigUpdateView(LoginRequiredMixin, UpdateView):
model = Building
fields = ('label','address',)
template_name = "buildings/building_update.html"
form_floors = FloorsConfigForm(prefix="floor_")
def get_context_data(self, **kwargs):
ret = super(BuildingConfigUpdateView, self).get_context_data(**kwargs)
ret["form_floors"] = self.form_floors
b = Building.objects.get(id=self.kwargs["pk"])
ret["floors"] = Floor.objects.filter(building=b)
return ret
def post(self, request, *args, **kwargs):
res = None
print(request.POST)
if "action_save" in request.POST:
res = super(BuildingConfigUpdateView, self).post(request, *args, **kwargs)
elif "action_add_floor" in request.POST:
# #add the floor , validate, save...
self.form_floors = FloorsConfigForm(request.POST, prefix="floor_")
if self.form_floors.is_valid():
self.form_floors.save()
res = HttpResponseRedirect(reverse('buildings:config', kwargs={"pk":self.kwargs["pk"]}))
else:
print(self.form_floors.errors)
res = HttpResponseRedirect(reverse('buildings:config', kwargs={"pk":self.kwargs["pk"]}))
else:
res = HttpResponseRedirect(reverse('buildings:config', kwargs={"pk":self.kwargs["pk"]}))
return res
This works very well, and I am happy(!)
BUT the last mile is to properly display the errors of my form_floors form in the template of its "parent".
And this is where I need you!
Note: in the view code above, I simply print the form error (print(self.form_floors.errors), and this is OK since it print
"<QueryDict: {'csrfmiddlewaretoken': ['gcBxn72bm6cLFXmpIatplZ0cNtsNgMlU'], 'floor_-building': [''], 'action_add_floor': [''], 'floor_-label': ['']}>
<ul class="errorlist"><li>building<ul class="errorlist"><li>This field is required.</li></ul></li></ul>"
when I dont specify a building to the floor.
The exact question is: how can I render this error message in the main template?
Edit - Getting closer
In my post def in view, I changed the Redirects:
def post(self, request, *args, **kwargs):
res = None
print(request.POST)
if "action_save" in request.POST:
res = super(BuildingConfigUpdateView, self).post(request, *args, **kwargs)
elif "action_add_floor" in request.POST:
# #add the floor , validate, save...
self.form_floors = FloorsConfigForm(request.POST, prefix="floor_")
if self.form_floors.is_valid():
self.form_floors.save()
res = HttpResponseRedirect(reverse('buildings:config', kwargs={"pk":self.kwargs["pk"]}))
else:
print(self.form_floors.errors)
res = super(BuildingConfigUpdateView, self).post(request, *args, **kwargs)
else:
res = HttpResponseRedirect(reverse('buildings:config', kwargs={"pk":self.kwargs["pk"]}))
return res
It's better, I have access to the errors in the template. But my main form is empty!
It's emplty because in my template, I have two html forms. So, when I send the second form, the POST request don't have the other kwargs.
Now, I need to find a way to "fusion" two requests!
I have the following Abstract:
class A:
clientname ...
logs = models.ManyToManyField(B, blank=True, null=True)
class B.
message ...
timestamp = models.DateTimeField()
def save(self, *args, **kwargs):
self.timestamp = datetime.now()
super(B, self).save(*args, **kwargs)
Now, if a message is saved I want it to have a timestamp, always. And I do not want to change the many2many relationship. Is this possible? And if It is how would I go about writing it?
UPDATE:
my view
def log(request):
if request.method == 'POST':
log, created = B.objects.get_or_create(message=request.POST['message'])
client = \
A.objects.get(clientname=request.POST['clientname'])
client.logs.add(log)
return HttpResponse(content="OK", mimetype="text/plain", status=200)
else:
return HttpResponse(content="Failed", mimetype="text/plain", status=400)
RESOLUTON:
I did it, this is kind of a workaround:
def log(request):
if request.method == 'POST':
time = datetime.now()
log, created = \
Log.objects.get_or_create(message=request.POST['message'], \
timestamp=time)
client = \
Thinclient.objects.get(hostname=request.POST['clientname'])
client.logs.add(log)
return HttpResponse(content="OK", mimetype="text/plain", status=200)
else:
return HttpResponse(content="Failed", mimetype="text/plain", status=400)
UPDATE:
No actually that wasn't what I wanted either because this will create new message instances even if the message is the same
You can use the DateTimeField with auto_now argument:
timestamp = DateTimeField(auto_now=True)
More about this:
https://docs.djangoproject.com/en/1.3/ref/models/fields/#django.db.models.DateField.auto_now
add save() to the class B
def save(self, *args, **kwargs):
self.timestamp = datetime.now()
super(B, self).save(*args, **kwargs)