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')
Related
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
Background
I'm trying to load a custom url (e.g. www.mysite.com/order-2523432) that will show a user details about their order.
Problem
I am trying to use the method order_id in my models.py in order to get the correct url. The problem is that I am getting the error:
'OrderDetailView' object has no attribute 'order_id'
Does anyone know what I can do to get order_id to work?
My views.py:
class OrderDetailView(DetailView):
model = Orders
template_name = "customer/orders.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
try:
context["orders"] = get_orders(self)
except RequestException as e:
logger.exception(e)
return context
My utils.py:
def get_orders(orders):
url = f"mysite.com/customer/{orders.order_id}"
method = "GET"
content_type = "application/json"
header = Sender(
credentials etc
).request_header
response = requests.request(
headers etc
)
response.raise_for_status()
return response.json()
My models.py:
class Orders(CustomModel):
table_name = models.CharField(max_length=256, unique=True)
#property
def order_id(self):
return f"order-{self.table_name}"
def get_absolute_url(self):
return reverse("order:edit", args=(self.id,))
you should use self.object or context['object'] or get_object() instead of passing self
please try this:
class OrderDetailView(DetailView):
model = Orders
template_name = "customer/orders.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
try:
context["orders"] = get_orders(context['object'])
except RequestException as e:
logger.exception(e)
return context
I am not too sure if this is possible or not. Using stripes API singing up a user requires an ID picture, front and back, so here is my form for that:
class IDuploadForm(forms.Form):
id_front = forms.ImageField()
id_back = forms.ImageField()
here is an example of creating a file in stripe from the API:
with open("/path/to/a/file.jpg", "rb") as fp:
stripe.File.create(
purpose="dispute_evidence",
file=fp
)
My goal is to not have to store the users images on my own server, as they are ID documents, and just be able to send them right to the stripe API, but I have tried this and get errors from stripes side of things
Here is the view I have wrote but does not work(think i am not handling file correctly):
class CreateStripeAccount_ID(View):
form = IDuploadForm
template_name = 'id_upload.html'
def get(self, request, *args, **kwargs):
form = self.form()
return render(request, self.template_name, {'form':form})
def post(self, request, *args, **kwargs):
form = IDuploadForm(request.POST, request.FILES)
if form.is_valid():
krop = Krop.objects.get(owner=self.request.user)
str_acc_id = krop.stripe_acc_id
id_front = form.cleaned_data['id_front']
id_back = form.cleaned_data['id_back']
stripe.api_key = settings.STRIPE_TEST_SEC_KEY
#file_id_front = stripe.File.create(
# purpose='identity_document',
# file=id_front
# )
#file_id_back = stripe.File.create(
# purpose='identity_document',
# file=id_back
# )
return redirect('home')
return redirect('home')
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!